React:记账本05---数据持久化与数据展示

282 阅读3分钟

数据持久化

  1. 为什么要做数据持久化?
  • 因为不持久,数据存在内存,一刷新就没有了
  • 这样可以实现整个项目不同页面数据的统一
  1. 怎么做?
  • 使用自定义Hooks 结合 Localstorage

标签页数据持久化

1. addTag封装进useTag

    const addTag=()=>{
        const tagName = window.prompt('新标签的名称为');
        if(tagName !== null  && tagName !== ''){
            setTags([...tags,{id:createId(),name:tagName}])
        }
    };

2. 什么时候把tags放入localstorage里呢?使用useEffect帮你解决

  1. 只要tags变了,就把tags放入localStorage里
  • localStoarge只能存string,所以使用JSON.stringify
    • 使用一个依赖为[tags]的useEffect
        useEffect(()=>{
            console.log('tags change')
            console.log(tags)
            window.localStorage.setItem('tags',JSON.stringify(tags))
        },[tags]) //依赖变化的时候执行
    
  • 再增加一个useEffect,让依赖为[]
    • []:组件挂载时执行,只在mount执行一次,就不再执行了
     useEffect(()=>{
            console.log('after mount')
            let localTags = JSON.parse(window.localStorage.getItem('tags') || '[]')
            setTags(localTags)
        },[])// 组件挂载时执行,只在mount执行一次,就不再执行了
    
  • 两个useEffect一起,如果增加一个tag,会发生什么?
    • 先打印 after mount:依赖为[]的useE
    • 打印 tags change :依赖为[tags]的useEffect
    • 打印[] : tags从undefied变为 []
    • 打印 tags change:依赖为[tags]的useEffect
    • 打印新增加的tags对象: []变为新增对象
  • 第一次打印[]是多余的,不需要打印第一次的[]
    • 声明一个ref,每次useEffect加1,
    • 等于1说明是第一次渲染,第一次渲染什么也不做。
    • 只有ref大于1,才执行useEffect
    • 封装为一个hook,useUpdate。就解决了那个多余的set
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]) //tags必须是不可变值,每次修改是一个新的对象,在【tags】变化的时候执行
}
export {useUpdate}

  • 所以useTags变为
   //只在mount执行一次,就不再执行了
    useEffect(()=>{
        let localTags = JSON.parse(window.localStorage.getItem('tags') || '[]')
        // 如果长度为0,就是空,push预设值
        if(localTags.length ===0){
            localTags = [
                {id:createId(), name:'衣'},
                {id:createId(), name:'食'},
                {id:createId(), name:'住'},
                {id:createId(), name:'行'}
            ]
        }
        // 把localStoarge的tags读出,然后setTags
        setTags(localTags)
    },[])// 组件挂载时执行,只在mount执行一次,就不再执行了
    // 更新的时候执行这个
    useUpdate(()=>{
       // console.log('set')
        window.localStorage.setItem('tags',JSON.stringify(tags))
    },tags)

3. 注意tags.tsx执行次数

  • 进入标签页面tags do it会执行2次,第一次tags从undeined[],第二次[]到初始化设定的值
function Tags() {
   const {tags,addTag} = useTags()
    console.log('tags do it')
    return (
        <Layout>
            <TagList>
                {tags.map(tag=>
                    <li key={tag.id}>
                        <Link to={'/tags/'+tag.id}>
                            <span className="oneLine">{tag.name}</span>
                            <Icon name="right"></Icon>
                        </Link>

                    </li>
                )}
            </TagList>
            <Center>
                <Space/>
            <Button onClick={addTag}>新增标签</Button>
                <Space/>
            </Center>
        </Layout>

    );
}

4. 流程整理

  • 进入标签页面tags do it会执行2次,第一次tags从undeined[],第二次[]到初始化设定的值
  • 每次切换页面,组件就要重新挂载一次,也就是useTags里的useEffect就会起作用,设定初始值
    • localStorage里取,如果取得时候发现是空的,就push初始值
  • 如果对tags进行更新,就会触发useUpdate
    • 并存进localStorage
    • 只有设定的ref大于1的时候(即从[]变为tags时),才存进localStorage

记账页数据持久化

1. 新建useRecord.tsx hooks

  • 暴露records, addRecord
import {useEffect, useState} from "react";
import {useUpdate} from "./useUpdate";

// 新记录的类型
type newRecordItem = {
    tagIds:number[],
    note:string,
    category:"+" | '-',
    amount:number,
}
export 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)=>{
        if(newRecord.amount <=0){
            alert('请输入正确金额  ')
            return false
        }
        if(newRecord.tagIds.length === 0){
            alert('请选择标签')
            return false
        }
        const record = {...newRecord,createdAt:(new Date()).toISOString()}
        setRecords([...records,record])
        return true
    }
    return {records, addRecord}
}
export {useRecords}

2. 记账页点击ok提交record

   const submit=()=>{
       if(addRecord(selected)) {
           alert('保存成功')
           setSelected(defaultFormData)
       }

    }
<NumberpageSection value={selected.amount}
                onChange={(amount)=>onChange({amount})}
                    onOk={submit}
/>

3. 新增记录的逻辑

  • 在第一次渲染,挂载组件时,要去localStorage里读取records
  • money组件中,输入记录,点击ok调用addRecord
  • useRecord的hook里,addRecord中会setRecords,更新record,触发更新
  • 让money组件再次执行,money组件就可以获取新的records
  • 同时,在useRecords中,调用useUpdate,依赖records, 当record变了,就把新的records更新到localStorage