我已经很长时间享受React Hooks了。它们提供了以非常清晰和简洁的方式编写声明性逻辑的机会。例如,考虑使用此自定义useHistory
钩子,该钩子记录传入的值的历史记录。
const useHistory = val => {
const [history, setHistory] = useState([]);
useEffect(() => {
setHistory(history => {
return [...history, val];
});
}, [val]);
return history;
};
可以这样使用它来显示一段时间内所有先前的“计数”值(Code sandbox):
function App() {
const [count, setCount] = useState(0);
const history = useHistory(count);
const historyStr = history.join(", ");
return (
<div className="App">
<div>History: {historyStr}</div>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
该应用程序运行正常。但是,它有一个隐藏的缺陷。由于useEffect
挂钩在渲染之后称为 ,因此,每次单击按钮时,应用都会渲染两次。
这只是一个玩具的例子。在大型应用程序中,useEffect
依赖项的复杂链会导致级联渲染,这绝对会降低性能。
useEffect
似乎主要是为副作用而设计的,documentation中没有太多内容可以表明是否可以接受useEffect
挂钩中的更新状态。
话虽这么说,使用useEffect
钩子来处理状态逻辑所提供的表现力(就像我们在useHistory
示例中看到的那样)非常吸引人,并且(似乎)不可能使用其他解决方案进行复制。
我想念什么吗?有什么方法可以实现这种模式而又不会导致级联渲染?
答案 0 :(得分:0)
传递给useEffect挂钩的函数将在每个渲染周期执行。加上onClick事件,将调用useState,该结果将渲染组件(第一个渲染),然后将执行useEffect函数,该函数将更改状态,然后再次渲染组件。因此,我认为您应该避免在两个函数中更新同一事件的状态。在我看来,useEffect对于诸如发送http请求之类的副作用很有用,而不是为了更新用户操作的状态。
我建议作为解决方案:
function App() {
// Not necessary to save count because count = history[history.length - 1]
const [history, setHistory] = useState([]);
const historyStr = history.join(", ");
console.log("render");
return (
<div className="App">
<div>History: {historyStr}</div>
<button
onClick={() => setHistory(prev => [...prev, prev[prev.length - 1] - 1 ])}>
Decrement
</button>
<button onClick={() => setHistory(prev => [...prev, prev[prev.length - 1] + 1 ])}>Increment</button>
</div>
);
}
答案 1 :(得分:0)
这是我看到的内容:单击按钮时,它会更改状态(计数)并重新渲染组件。重新渲染后,跟随[val]变化的useEffect会看到[val]变化,并在内部调用setHistory。这会再次更改您的状态(历史记录)并重新渲染您的组件。我认为,不管使用useEffect,由于您的应用程序设计,您都必须渲染两次:您的第一个状态更改将触发另一个状态更改。每个状态更改=组件都会重新呈现。