写了两个react hook,大家帮忙品品
详情
事情是这样的,某天我在参与一个react-native项目开发时,碰到了一个问题:useState更新状态后获取不到最新的状态值,useMemo同样也有这个问题,如下所示:
import { useEffect, useState, useMemo } from 'react'
export default MyComponent = () => {
const [state, setState] = useState(0)
const memo = useMemo(() => {
return state + 100
}, [state])
useEffect(() => {
setState(1)
console.log(state) // 0
console.log(memo) // 100
}, [])
return (
<>
<div>{ state }</div>
<div>{ memo }</div>
</>
)
}
之所以会出现这种情况,是因为setState()这个函数实际上是用新状态去进行另一次渲染,而不会对当前已执行的程序中的状态值造成影响。官方也给出了解决的办法:

翻译过来,就是说想要使用新状态的话,可以提前用一个变量去保存新状态。这样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([])
const [ state2, setState2 ] = useSyncState([])
let request1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
state1.current = [{x: 1}]
resolve()
}, 500)
})
}
let request2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
state2.current = [{ y: 2 }]
resolve()
}, 1000)
})
}
useEffect(() =>{
Promise.all([request1(), request2()]).then(res => {
setState1([{ ...state1.current[0], ...state2.current[0] }])
setState2(state2.current)
// 这里虽然setState了两次,但是react会合并所有setState,只发起一次重新渲染。
// 如果是在request中直接setState则会重新渲染两次
})
}, [])
return (
<>
<div>{ state1.state }</div>
<div>{ state2.state }</div>
</>
)
}
我:useSyncState()和setState()的参数形式也和原来的hook是一样的,可以传值也可以传一个函数,用法与原来的hook几乎是一样的,可以自己试试。
总结
useSyncState()和useSyncMemo()的封装、优化逻辑完全符合react官方的要求,并且维持了与原来hook一样的写法,但在用法上略有不同,是通过state.state调用状态以及state.current调用当前状态值,基于这种形式,你将不需要再担心复杂的状态数据流动问题,可对代码逻辑带来一些优化。有需要的小伙伴可以试试看react-sync-state-hook。
写在最后,因为useSyncState()会多带来一个闭包变量state.current,因此虽然state.state和普通状态没什么区别,但如果用不上state.current的话,项目中就还是用useState()定义状态,我自己也是这样使用的。
转载自:https://juejin.cn/post/7209504489010921530