React旺财记账-数据持久化与数据展示

更优雅的更新与删除

更新

不优雅版

const updateTag = (id: number, obj: { name: string }) => {
    //获取要修改的tag的下标
    const index = findTagIndex(id);
    //深拷贝tags
    //vue可以直接在原数据上修改,但react不支持这项功能,因为它认为数据不可变
    const tagsClone = JSON.parse(JSON.stringify(tags))
    //把tagsClone的第index个删掉,换成{id: id, name: obj.name}
    tagsClone.splice(index, 1, {id: id, name: obj.name})
    setTags(tagsClone)
}
复制代码

优雅版

const updateTag = (id: number, obj: { name: string }) => {
    setTags(tags.map(tag => tag.id === id ? {id, name: obj.name} : tag));
}
复制代码

删除

不优雅版

const deleteTag = (id: number) => {
    //获取要删除的tag的下标
    const index = findTagIndex(id);
    //深拷贝tags
    //vue可以直接在原数据上修改,但react不支持这项功能,因为它认为数据不可变
    const tagsClone = JSON.parse(JSON.stringify(tags))
    //把tagsClone的第index个删掉
    tagsClone.splice(index, 1)
    setTags(tagsClone)
}
复制代码

优雅版

const deleteTag = (id: number) => {
    setTags(tags.filter(tag => tag.id !== id))
}
复制代码

增加编辑页面回退功能

为了使icon可以点击,需要修改Icon组件,即自定义Icon需要继承SVG所有属性

问题在于,当使用 ...rest时,如果rest里拥有className那就会覆盖原本的className,为了解决这个问题,需要添加组件

yarn add classnames
yarn add --dev @types/classnames
复制代码

注意:保持版本号一致

更新后的Icon代码:

  
import React from 'react';
import cs from 'classnames';

let importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {importAll(require.context('icons', true, /\.svg$/));} catch (error) {console.log(error);}

type Props = {
  name?: string
} & React.SVGAttributes<SVGElement>

const Icon = (props: Props) => {
  const {name, children, className, ...rest} = props
  return (
    <svg className={cs('icon', className)} {...rest}>
      {props.name && <use xlinkHref={'#' + props.name}/>}
    </svg>
  );
};

export default Icon;
复制代码

解决createId刷新后id重置问题

解决方法是记住最后一次的id值,下次直接从 localStorage 中读取

let id = parseInt(window.localStorage.getItem('idMax') || '0')
const createId = () => {
    id += 1;
    window.localStorage.setItem('idMax', id.toString())
    return id;
}

export {createId}
复制代码

解决标签持久化问题

自定义useUpdate

页面第一次默认刷新后,当deps改变时进行fn()

import {useEffect, useRef} from "react";

const useUpdate = (fn: () => void, deps: any[]) => {
    const count = useRef(0)
    useEffect(() => {
        count.current += 1;
    })
    useEffect(() => {
        if (count.current > 1) {
            fn()
        }
    }, deps);//这里必须是不可变数据
}
export {useUpdate}
复制代码

读取localStorage并新增

注意默认标签的使用方法,即当localStorage为空时设定默认标签

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)
}, [])
useUpdate(() => {
    window.localStorage.setItem('tags', JSON.stringify(tags))
}, [tags])//组件挂载时执行
复制代码

解决记账页面持久化问题

之前没有写点击ok后会发生什么,现在进行补充。在点击ok后,数据应该被存入localStorage中,即进行submit操作

const submit = () => {
    addRecord(selected)
    alert('保存成功')
    setSelected(defaultFormdata)
}
复制代码

其中addRecord是自定义hook中的功能

import {useEffect, useState} from "react";
import {useUpdate} from "./useUpdate";

type newRecordItem = {
    tagIds: number[]
    note: string
    category: '+' | '-'
    amount: number
}

type RecordItem = newRecordItem & {
    createdAt: string//格式为ISO 8601
}


const useRecords = () => {
    const [records, setRecords] = useState<RecordItem[]>([]);
    useEffect(() => {
        setRecords(JSON.parse(window.localStorage.getItem('records') || '[]'))
    }, [])
    useUpdate(() => {
        window.localStorage.setItem('records', JSON.stringify(records))
    }, [records])
    const addRecord = (newRecord: newRecordItem) => {
        const record = {...newRecord, createdAt: (new Date()).toISOString()}
        setRecords([...records, record])
    }
    return {records, addRecord,}
}

export {useRecords}
复制代码

小技巧:如果两种类型很像,想不重复代码,第一种方法是上面代码使用的 & 技巧,还可以把多的类型中的某几个删除,代码为type newRecordItem = Omit<RecordItem, 'createdAt' | 'updatedAt'>

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享