数据持久化
- 为什么要做数据持久化?
- 因为不持久,数据存在内存,一刷新就没有了
- 这样可以实现整个项目不同页面数据的统一
- 怎么做?
- 使用自定义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帮你解决
- 只要tags变了,就把tags放入localStorage里
- localStoarge只能存string,所以使用
JSON.stringify
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)
},[])
- 两个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])
}
export {useUpdate}
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)
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
就会起作用,设定初始值
- 去
localStorag
e里取,如果取得时候发现是空的,就push初始值
- 如果对
tags
进行更新,就会触发useUpdate
- 并存进
localStorage
- 只有设定的ref大于1的时候(即从[]变为tags时),才存进
localStorage
记账页数据持久化
1. 新建useRecord.tsx hooks
import {useEffect, useState} from "react";
import {useUpdate} from "./useUpdate";
type newRecordItem = {
tagIds:number[],
note:string,
category:"+" | '-',
amount:number,
}
export type RecordItem = newRecordItem & {
createdAt:string
}
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