【发布时间】:2020-09-23 09:24:20
【问题描述】:
我对 ReactJS 非常熟悉,但刚刚开始接触 Hooks。
有时对我来说困难的是如何将命令式 API 封装到钩子中。我感觉有时我使用了错误的方法。
例如,从现实世界的应用程序中获取这个大大简化的示例。假设有一个全局状态 API(不能更改):
-
server.getState(name)以同步方式返回最后一个已知状态(假设是一个字符串) -
server.on(name, callback)为该状态的更改注册一个事件处理程序 -
server.off(name, callback)取消注册该事件处理程序
我想创建一个自定义挂钩 useServerState(name, defaultValue),它返回该状态的实时值。
我的实现如下所示:
function useServerState(name, defaultValue) {
const [ curState, setCurState ] = useState(() => server.getState(name) || defaultValue);
useEffect(
() => {
const evHandler = payload => setCurState(payload);
// register on mount:
server.on(name, evHandler);
// de-register on unmount:
return () => server.off(name, evHandler);
},
[ name ]
);
return curState;
}
用法:
function SomeComponent() {
const temperature = useServerState("temp_outside", "unknown");
return <div>
Current temperature: {temperature} degrees
</div>;
}
问题
这可行,但问题是useEffect() 似乎异步调用函数。在我的 useEffect() 处理程序运行之前会有一些延迟,例如 50 毫秒 左右。在此期间我的钩子对于可能同时出现的新事件是盲目的(因为尚未注册事件处理程序),这意味着即使它已经被钩子也可能返回旧状态更新了。
一种(不优雅的)解决方法
我可以通过再次调用 getState() 来解决这个问题在 useEffect() 处理程序中(换句话说,在“mount”上):
function useServerState(name, defaultValue) {
const getInitialState = () => server.getState(name) || defaultValue;
const [ curState, setCurState ] = useState(getInitialState);
useEffect(
() => {
const evHandler = payload => setCurState(payload);
// register on mount:
server.on(name, evHandler);
// ----->
// anti race condition hack:
const nv = getInitialState();
if (nv !== curState)
setCurState(nv);
// <-----
// de-register on unmount:
return () => server.off(name, evHandler);
},
[ name ]
);
return curState;
}
另一个(不好的)解决方法
另一种解决方案是始终使用固定的起始值并仅在挂载时读取当前状态:
function useServerState(name, defaultValue) {
const [ curState, setCurState ] = useState(null);
useEffect(
() => {
const evHandler = payload => setCurState(payload);
// register on mount:
server.on(name, evHandler);
// set initial state:
setCurState(server.getState(name) || defaultValue);
// de-register on unmount:
return () => server.off(name, evHandler);
},
[ name ]
);
return curState;
}
这个解决方案的缺点是它总是会导致两次渲染:一个带有null,然后另一个带有实际状态。这是不希望的。
如何改进?
解决方法对我来说似乎很尴尬,让我想知道是否有更好的方法来做到这一点,而不涉及解决方法。我希望 React 团队已经考虑过这种情况。我错过了什么?
【问题讨论】:
-
第一个(简单的)解决方案不仅会遇到延迟,而且当
name更改时,也不会根据新名称更新状态(重置状态)。如果您问我,您的“不雅”解决方案似乎很合理。唯一的问题是如何实施平等检查。 -
同意,我错过了这样一个事实,即“不优雅”的解决方案也需要注意名称更改。这样看来,解决方案似乎不那么老套了。
-
根据服务器的实际状态,甚至可能不需要像react does that for you already 那样进行额外的相等性检查。用相同的值调用
useState的setter 不会触发重新渲染。但是,如果它是一个对象,您需要自己实现相等性检查,因为 react 通过引用比较它们。 -
我错过了这个细节,谢谢!
标签: javascript reactjs react-hooks