Q1:如何在重新渲染之前对某些状态进行深度比较?
在下面的 sn-p 中,我向您展示了一种对状态进行深入比较的方法。
Q2:useCallback、useMemo的原理是什么?
useMemo 旨在用于您需要进行一些昂贵的计算并想记住结果的情况。因此,对于inputs 的每个独特组合,昂贵的计算只运行一次。
useCallback 旨在避免重新创建不需要重新创建的内部函数。除非依赖数组中列出的某些变量发生变化。它通常用作性能优化。
根据 React DOCS:
使用回调
这在将回调传递给优化的子组件时很有用,这些子组件依赖于引用相等来防止不必要的渲染(例如 shouldComponentUpdate)。
注意:
状态变化会导致重新渲染。为什么要在重新渲染之前对其进行深入比较?如果它改变了,React 必须重新渲染。这就是它的工作原理。
如果您想要更改某些内容而不导致重新渲染,请查看 useRef 挂钩。这正是它的作用。 ref 对象在每次渲染中都保持不变(只有在组件被卸载然后重新安装时才会改变)。
https://reactjs.org/docs/hooks-reference.html#useref
使用参考
useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传递的参数(initialValue)。返回的对象将在组件的整个生命周期内持续存在。
(...)
不过,useRef() 的用处不止是 ref 属性。 保持任何可变值类似于您在类中使用实例字段的方式非常方便。
片段
比较 newState 和 lastState
function App() {
const [myState, setMyState] = React.useState('Initial state...');
const lastState_Ref = React.useRef(null);
const stateChanged_Ref = React.useRef(null);
if (lastState_Ref.current !== null) {
lastState_Ref.current === myState ? // YOU CAN PERFORM A DEEP COMPARISON IN HERE
stateChanged_Ref.current = false
: stateChanged_Ref.current = true;
}
lastState_Ref.current = myState;
function handleClick() {
setMyState('New state...');
}
return(
<React.Fragment>
<div>myState: {myState}</div>
<div>New state is different from last render: {JSON.stringify(stateChanged_Ref.current)}</div>
<button onClick={handleClick}>Click</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
更新:
根据您的评论,对于props,您可以使用React.memo。它会对你的道具做一个浅显的比较。请参阅下面的 sn-p。您会看到,常规的 Child 每次都会重新渲染,如果数组 propA 保持不变,则包裹在 React.memo 中的 ChildMemo 不会重新渲染。
React.memo
React.memo 是一个高阶组件。它与 React.PureComponent 类似,但用于函数组件而不是类。
如果你的函数组件在给定相同的 props 的情况下呈现相同的结果,你可以将它包装在对 React.memo 的调用中,以便在某些情况下通过记忆结果来提高性能。这意味着 React 将跳过渲染组件,并重用上次渲染的结果。
默认情况下,它只会对 props 对象中的复杂对象进行浅层比较。如果您想控制比较,您还可以提供自定义比较函数作为第二个参数。
这就是 React DOC 对这种情况的建议:
https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-shouldcomponentupdate
function App() {
console.log('Rendering App...');
const [myState,setMyState] = React.useState([1,2,3]);
const [myBoolean,setMyBoolean] = React.useState(false);
return(
<React.Fragment>
<button onClick={()=>setMyBoolean((prevState) => !prevState)}>Force Update</button>
<Child
propA={myState}
/>
<ChildMemo
propA={myState}
/>
</React.Fragment>
);
}
function Child() {
console.log('Rendering Child...');
return(
<div>I am Child</div>
);
}
const ChildMemo = React.memo(() => {
console.log('Rendering ChildMemo...');
return(
<div>I am ChildMemo</div>
);
});
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
关于状态,我认为您不应该尝试实现这一点。如果状态发生变化,它应该重新渲染。实际上,如果(对象或数组的)状态引用保持不变,React 不会重新渲染。因此,如果您不更改 state 引用(例如从头开始重新创建相同的数组或对象),那么您已经拥有了开箱即用的行为。你可以在下面的 sn-p 上看到:
function App() {
console.log('Rendering App..');
const [myArrayState,setMyArrayState] = React.useState([1,2,3]);
function forceUpdate() {
setMyArrayState((prevState) => {
console.log('I am trying to set the exact same state array');
return prevState; // Returning the exact same array
});
}
return(
<React.Fragment>
<div>My state is: {JSON.stringify(myArrayState)}</div>
<button onClick={forceUpdate}>Reset State(see that I wont re-render)</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>