React之自定义hooks

254 阅读2分钟

我在money-memory项目做了三个定义hooks: useUpdate、useRecords、useTags

1. useUpdate: 用内置Hook useEffect和useRef封装

  • 用途:用来模拟class组件中的componentDidUpdate()
  • 实现思路:单纯使用useEffect来模拟componentDidUpdate()会导致数据从无到有的时候也会触发fn,使用useRef,在触发fn之前加个判断条件
import {useEffect, useRef} from 'react';

const useUpdate = (fn: () => void, dependency: any[]) => {
  const count = useRef(0);
  useEffect(() => {
    count.current += 1;
  });

  useEffect(() => {
    if (count.current > 1) {
      fn();
    }
  }, [fn, dependency]);
};
export {useUpdate};

2. useRecords: 用内置Hook(useEffect、useRef)和自定义Hook(useUpdate)封装

  • 用途:获取和添加记账页面的数据,包括标签、备注、收入或者支出、金额,以及记账时间,使用一个对象存储这些数据
import {useEffect, useState} from 'react';
import {useUpdate} from './useUpdate';

// 声明数据类型
type RecordItem = {
  tagIds: number[];
  note: string;
  category: '+' | '-';
  amount: number;
  createAt: string; // ISO 8601
}
// 声明类型,剔除了createAt
type NewRecordItem = Omit<RecordItem, 'createAt'>

export const useRecords = () => {
  const [records, setRecords] = useState<RecordItem[]>([]);
  // 模拟componentDidMount, 组件挂载后从localStorage获取数据records
  useEffect(() => {
    setRecords(JSON.parse(window.localStorage.getItem('records') || '[]'));
  }, []);
  // 模拟componentDidUpdate, records更新后将数据写入localStorage
  useUpdate(() => {
    window.localStorage.setItem('records', JSON.stringify(records));
  }, records);
  // 用户完成一次记账操作,将新数据添加到records
  const addRecord = (newRecord: NewRecordItem) => {
    if (newRecord.amount <= 0) {
      alert('请输入金额');
      return false;
    }
    if (newRecord.tagIds.length <= 0) {
      alert('请选择至少一个标签');
      return false;
    }
    const record = {...newRecord, createAt: (new Date()).toISOString()};
    setRecords([...records, record]);
    return true;
  };
  return {
    records,
    addRecord
  };
};

3. useTags: 用内置Hook(useEffect、useRef)和自定义Hook(useUpdate), 以及自己封装的函数createId封装

  • 用途:用来创建标签,以及获取创建的标签,标签包括唯一的id,以及标签名name,存储在localStorage中

createId.tsx

// 将id存储到localStorage,对id进行统一的管理,避免重复
let id = parseInt(window.localStorage.getItem('idMax') || '0');
const createId = (): number => {
  id += 1;
  window.localStorage.setItem('idMax', JSON.stringify(id));
  return id;
};
export {createId};

useTags.tsx

import {useEffect, useState} from 'react';
import {createId} from 'lib/createId';
import {useUpdate} from './useUpdate';
// 对标签属性类型进行声明
type Tag = {
  id: number;
  name: string
}
const useTags = () => {
  const [tags, setTags] = useState<Tag[]>([]);
  useEffect(() => {
    let localTags = JSON.parse(window.localStorage.getItem('tags') || '[]');
    // 设置默认标签
    if(localTags.length === 0){
      localTags = [
        {id: createId(), name: '衣'},
        {id: createId(), name: '食'},
        {id: createId(), name: '住'},
        {id: createId(), name: '行'}
      ];
    }
    setTags(localTags);
  }, []);

  // tags更新的时候,存入到localStorage中
  useUpdate(()=>{
    window.localStorage.setItem('tags', JSON.stringify(tags));
  }, tags)
  // 通过id获取标签tag  
  const findTag = (id: number) => tags.filter(tag => tag.id === id)[0];
  // 通过id获取当前标签在标签组tags中的index
  const findTagIndex = (id: number) => {
    let result = -1;
    for (let i = 0; i < tags.length; i++) {
      if (tags[i].id === id) {
        result = i;
        break;
      }
    }
    return result;
  };
  // id不变,更新标签名
  const updateTag = (id: number, name: string) => {
    setTags(tags.map(tag => tag.id === id ? {id, name} : tag));
  };
  // 通过id寻找标签,删除标签
  const deleteTag = (id: number) => {
    setTags(tags.filter(tag => tag.id !== id));
  };
  // 添加标签,标签名不能为空
  const addTag = () => {
    const tagName = window.prompt('请输入标签名');
    if (tagName !== null && tagName !== '') {
      setTags([...tags, {id: createId(), name: tagName}]);
    }
  };
  // 通过id获取标签名
  const getName = (id: number) => {
    const tag = tags.filter(t => t.id === id)[0];
    return tag ? tag.name : '';
  }
  // 对外暴露接口
  return {
    tags,
    setTags,
    findTag,
    updateTag,
    findTagIndex,
    deleteTag,
    addTag,
    getName
  };
};

export {useTags};