likes
comments
collection
share

写了两个react hook,大家帮忙品品

作者站长头像
站长
· 阅读数 12

详情

事情是这样的,某天我在参与一个react-native项目开发时,碰到了一个问题:useState更新状态后获取不到最新的状态值,useMemo同样也有这个问题。我赶紧上网搜索答案,折腾了很久,最后终于在官方文档中找到了这么一段描述:

写了两个react hook,大家帮忙品品

翻译过来,就是说想要使用新状态的话,可以提前用一个变量去保存新状态。这样useState的问题就解决了,但是useMemo呢,useMemo要怎么搞?在经过一阵头脑风暴后,我有了那么一点点思路,我决定自己再封装两个hook来解决这个问题。说干就干,经过一段时间地不停试错和调试后,终于还是被我搞出来了,我欣喜若狂,马上拿给同事,于是有了下面的对话:

同事:你自己试了吗? 我:试了几次。 同事:感觉怎么样? 我:我修改了一部分用法,但是又保留了一部分,我觉得保留一部分写法习惯,你才知道你在用的是什么hook。 同事:你是有意把它保留的吗? 我:是我在写的过程中, 留下了一部分。 同事:是故意的还是不小心的? 我:是故意的。

于是同事试了一下

import { useEffect } from 'react'
import { useSyncState, useSyncMemo } from 'react-sync-state-hook'

export default MyComponent = () => {
    const [ state, setState ] = useSyncState(0)
    const [ state2, setState2 ] = useSyncState(() => 100)
    const memo = useSyncMemo(() => {
        return state.current + state2.current
    }, [state, state2])
    
    useEffect(() =>{
        setState(1)
        console.log(state.state)     // 0
        console.log(state.current)   // 1
        console.log(memo.state)      // 100
        console.log(memo.current)    // 101
    }, [])
    
    return (
        <>
            <div>{ state.state }</div>
            <div>{ memo.state }</div>
        </>
    )
}

我赶紧解释道:现在确实要比原先多写一个.state去调用状态,但这其实是不得已而为之的,主要是为了让useSyncMemo()能更方便地执行监听依赖和同步计算而做出的让步。现在即使是在复杂的循环和递归中,只需调用一次setState(),就能让current自动更新成最新的状态,memo也会随着依赖项的current更新而更新,像这样:

import { useEffect } from 'react'
import { useSyncState, useSyncMemo } from 'react-sync-state-hook'

export default MyComponent = () => {
    const [ state, setState ] = useSyncState(0)
    const memo = useSyncMemo(() => {
        return state.current + 1
    }, [state])
    
    useEffect(() =>{
        for(let i = 0; i < 5; i++){
            setState(i)
            console.log(state.state)     // 0 0 0 0 0
            console.log(state.current)   // 0 1 2 3 4
            console.log(memo.state)      // 0 0 0 0 0
            console.log(memo.current)    // 1 2 3 4 5
        }
    }, [])
    
    return (
        <>
            <div>{ state.state }</div>
            <div>{ memo.state }</div>
        </>
    )
}

我:我们也可以只对current做修改,最后再调用setState()修改状态的值,来避免因频繁修改状态值而增加重新渲染的次数,这样可以带来一点性能上的提升,像这样:

import { useEffect } from 'react'
import { useSyncState, useSyncMemo } from 'react-sync-state-hook'

export default MyComponent = () => {
    const [ state1, setState1 ] = useSyncState(0)
    const [ state2, setState2 ] = useSyncState(0)

    let request1 = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                state1.current = 1
                resolve()
            }, 500)
        })
    }

    let request2 = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                state2.current = 2
                resolve()
            }, 1000)
        })
    }

    useEffect(() =>{
        Promise.all([request1(), request2()]).then(res => {
            setState1(state1.current)
            setState2(state2.current)
            // 这里虽然setState了两次,但是react会合并所有setState,只发起一次重新渲染。
            // 如果是在request中直接setState则会重新渲染两次
        })
    }, [])

    return (
        <>
            <div>{ state1.state }</div>
            <div>{ state2.state }</div>
        </>
    )
}

我:useSyncState()和setState()的参数形式也和原来的一样,可以传值也可以传一个函数,用法与原来的hook几乎是一样的,可以自己试试。

最终在我苦口婆心地劝说下,同事终于妥协了。

总结

useSyncState()和useSyncMemo()维持了与原来hook一样的写法,但在用法上略有不同,是通过state.state调用状态以及state.current调用当前状态值,有需要的小伙伴可以试试看react-sync-state-hook