我在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};