react自定义hook解决useState无法获取最新状态的问题

1,698 阅读2分钟

一、前言

最近在做一个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