新的 React Hooks 困惑

New React Hooks confused

我是React hooks功能组件的新手,有一些问题需要解释一下,谢谢:

  1. 如何在重新渲染之前对某些状态进行深度比较?我看到 React.memo 只有第二个参数用于修改道具上的比较,但是状态如何?目前使用Object.is进行比较,我想修改这个功能

  2. useCallback、useMemo的原理是什么?我已经调查了 React 源代码,但仍然不知道它是如何做到这一点的。有人能给我一个简单的例子来说明它是如何知道在每个渲染器上缓存值的吗?

Q1:如何在重新渲染之前对某些状态进行深度比较?

在下面的代码片段中,我向您展示了一种对状态进行深入比较的方法。

Q2:useCallback、useMemo的原理是什么?

useMemo 用于需要进行一些昂贵的计算并希望记住结果的情况。因此,对于 inputs.

的每个唯一组合,昂贵的计算只运行一次

useCallback 是为了避免重新创建不需要重新创建的内部函数。除非依赖数组中列出的某些变量发生变化。它通常用作性能优化。

根据 React 文档:

useCallback

This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).

注:

状态更改旨在导致重新渲染。为什么要在重新渲染之前对其进行深度比较?如果它改变了,React 必须重新渲染。这就是它的工作原理。

如果您想要更改某些内容而不导致重新渲染,请查看 useRef 挂钩。这正是它的作用。 ref 对象在每次渲染中都保持不变(只有在组件被卸载然后重新安装时才会发生变化)。

https://reactjs.org/docs/hooks-reference.html#useref

useRef

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

(...)

However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

片段

比较 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。它将对您的道具进行浅层比较。请参阅下面的片段。你会看到常规 Child 每次都会重新渲染,而 ChildMemo 包裹在 React.memo 中,如果数组 propA 仍然存在,则不会重新渲染一样。

React.memo

React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.

If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.

By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.

这就是 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 引用(例如从头开始重新创建相同的数组或对象),那么您已经拥有了开箱即用的行为。您可以在下面的代码片段中看到:

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"/>

为了补充 cbdev420 所说的内容,这里有一个 link 备忘单,它很好地解释了如何使用大多数钩子,如果它可以帮助你的话https://react-hooks-cheatsheet.surge.sh/