-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(createUseStorageState): batched updates #2082
base: master
Are you sure you want to change the base?
Conversation
该 PR 对应的钩子为 useLocalStorageState/useSessionStorageState,其旧的行为(该 PR 之前的行为)是: const [name, setName] = useLocalStorageState<string>('key', {
defaultValue: '0',
});
setName(name + '1');
setName(name + '2');
setName(name + '3');
console.log(name);
// 输出:'03',行为和 useState 钩子相同 以及: const [name, setName] = useLocalStorageState<string>('key', {
defaultValue: '0',
});
setName(prev => prev + '1');
setName(prev => prev + '2');
setName(prev => prev + '3');
console.log(name);
// 输出:'03',行为和 useState 钩子不同 采用了该 PR 之后,useLocalStorageState/useSessionStorageState 的行为(下文简称“新的行为”)如下: const [name, setName] = useLocalStorageState<string>('key', {
defaultValue: '0',
});
setName(name + '1');
setName(name + '2');
setName(name + '3');
console.log(name);
// 输出:'03',行为和 useState 钩子相同 以及: const [name, setName] = useLocalStorageState<string>('key', {
defaultValue: '0',
});
setName(prev => prev + '1');
setName(prev => prev + '2');
setName(prev => prev + '3');
console.log(name);
// 输出:'0123',行为和 useState 钩子相同 综上,
所以,这个钩子最初被设计的行为是怎样的呢?如果新的行为是对的话,那么这个 PR 感觉会带来破坏性更改啊?(毕竟行为有很大的改变) @crazylxr 见哥 来看下,已经初步验证 |
emm,我建议这个到 v4 的时候再做改动,现在确实会有 break change |
@liuyib @crazylxr 看了下此 PR 的修复方案,解决了批量更新行为和setState不一致的问题👍。但是存在一个细节不够完美,状态值和storage的值没有保持同步。这是因为使用 |
@Damon0820 按照react的理念,传递给setState的函数应该是纯函数;setState执行后,真实的state在下次render时才变化,所以在 |
@miaolq 嗯,要再综合考虑下 |
看了下文档。react对 Component、 initialState 、updater 都要求是纯函数。当前 initialState 的具体实现为 getStoredValue 方法,内部读取了外部变量的值 |
这个文档里看不到哪里指出了 initialState 也得是纯函数啊,截图示意下?Component 尽量是纯函数,这算业界公认;updater 是纯函数你这里一提,想了下还可以理解。initialState 纯不纯函数感觉意义不大,原因见下文: 参照文档: 这里 initialState 里获取初始值,和上图推荐的“最后手段”,感觉是一个道理的。比如,不在 initialState 里获取初始值,把代码做如下更改: const [state, setState] = useState(getStoredValue);
useUpdateEffect(() => {
setState(getStoredValue());
}, [key]); 改成: const [state, setState] = useState();
useEffect(() => {
setState(getStoredValue());
}, [key]); (上面的伪代码应该是等价的)这不又成了 React 文档里提到的“最后手段”,所以 initialState 没必要保证纯函数。 另外,我的理解,initialState 只会执行一次,即使从外部读取 localStorage,在组件多次渲染过程中,initialState 拿到的结果也只有一次,不会导致多次 render 渲染的 jsx 不一致,这样来说也没必要保证纯函数。 如果有错误,还请指出~ |
抱歉,文档位置不准确,关于 initialState 是纯函数的说明在 setState api 这里。 按照 react 的理念,initialState是纯函数,当前的实现不满足纯函数。理论上,不是纯函数,都有潜在 bug 风险,在这边这个理论依然成立。因为该 hook 可能在不同的地方多次被调用,你不知道在哪个地方、哪个环节 storage 的值会被更改。对于使用该 hook 的同个组件,相同的输入,可能会有不同的输出。但是实际上,没有人会去改 storage 的值,所以实际用起来几乎没有 bug,即使开启了
感觉这里需要综合考虑下是否有必要保证 initialState, updater 是纯函数。 |
是这样呢。确实按照 React 的理念应该保证这仨是纯函数,你是对的。但是感觉 100% 追求逻辑上符合理论,这个 hook 就废了,正如你说的会存在这些问题,,,,感觉还是实践大于理论些。先放这吧,回头其他大佬们有时间了找他们聊聊 |
嗯啊,看看大佬们的建议。若有结论了同步一下哈~ |
[中文版模板 / Chinese template]
🤔 This is a ...
🔗 Related issue link
fix: #2389
useLocalStorageState: Call setState(function) twice, the result is incorrect.
☑️ Self Check before Merge