一、前言
最近在做一个react native项目,在此之前我本人是没有react项目经验的,所以过程中踩了不少坑。其中最难处理的莫过于使用useState更新状态后获取不到最新值的问题,如下代码:
import { useState, useEffect } from 'react'
export default MyComponent = () => {
const [ state, setState ] = useState(0)
useEffect(() => {
setState(1)
console.log(state) // 打印结果为:0
}, [])
return (
<div>{ state }</div>
)
}
之所以会出现这种情况,是因为setState()这个函数实际上是用新状态去进行另一次渲染,而不会对当前已执行的程序中的状态值造成影响。官方也给出了解决的办法,那就是用一个变量去将最新的状态保存下来,方法如下:
但是,随着业务越来越复杂,状态数量越来越多,如果每次都这样去手动保存新状态的话,会大大降低代码的简洁性和可读性。所以我们还需要再做一些处理,让变量能够自己去保存新状态,这样代码看起来才会更优雅一点。当然你也可以直接用官方提供的办法去写,这个完全是看个人需要,这里我只是多提供一种选择,希望能帮到有需要的人。
二、处理方案
先看下最终封装成的hook:
const [ state, setState, current ] = useSyncState(initVal)
其中state就是我们平时使用的状态值,current是保存最新状态值的变量,setState是同步更新状态值和变量值的方法,这也是最关键的一步。封装如下:
import { useState, useRef, useCallback } from 'react'
const useSyncState = ( initVal ) => {
// 状态值
const [ state, setState ] = useState(initVal)
// 变量值
let current = useRef(initVal)
// 同步 state 和 current 的值
const setState = useCallback(( changeVal ) => {
current.current = changeVal
setState(changeVal)
}, [])
// 返回一个数组
return [ state, setState, current ]
}
在代码中使用:
export default MyComponent = () => {
const [ state, setState, current ] = useSyncState(0)
useEffect(() =>{
setState(1)
console.log(state) // 0
console.log(current.current) // 1
}, [])
return (
<div>{ state }</div>
)
}
有一点要注意的是,如果想直接修改current的话,最好配合immer等库进行复制后再修改,切记修改完后要再调用一次setState方法同步一下状态值。事实上先对current做修改最后再同步状态值的用法,是可以带来一些性能上的优化的,因为这样能减少因直接修改状态值而带来的渲染次数。
自定义 hook 我已经放到 github 上了,还做了一些边缘化处理,方便自己下次使用,后面也会补充useMemo的同步解决方案,有需要的可以看看:react-sync-state-hook。