【问题标题】:New React Hooks confused新的 React Hooks 困惑
【发布时间】:2019-07-30 03:02:27
【问题描述】:

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

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

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

【问题讨论】:

  • 既然你问了 4 个非常广泛的问题,我希望下面的链接对你有帮助 reactjs.org/docs/hooks-intro.html reactjs.org/docs/hooks-faq.html#performance-optimizations 也许你想把你的帖子分成多个问题,或者通过一些例子来展示它们是如何在一起的你的情况。
  • @karfau,感谢您的回复,我之前已经阅读过该帖子,但它没有提供自定义 prevState 和 nextState 的比较方法的方法 useState。我们只能修改 React.memo HOC 的 areEqual 方法,但这仅用于比较道具
  • 您的问题 #1 是关于“getSnapshotBeforeUpdate”方法的?
  • @Alexandre Aragão,不,我的 #1 是关于类组件中的 shouldComponentUpdate ,我们有 nextState 和 nextProps 然后我们可以做任何比较方法来决定它是否重新渲染。但是在带有钩子的功能组件中,我刚刚找到了 React.memo 的第二个参数,但它只能比较道具,而不是状态
  • "getSnapshotBeforeUpdate" 为您提供状态比较。但是在 ReactJS v16.8.0 版本中没有实现。检查this

标签: reactjs comparison react-hooks


【解决方案1】:

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

【讨论】:

  • 嗨@cbdev420,感谢您的详细解释。对于 Q1,我想要的是防止在状态更改时重新渲染 newState 等于 prevState (来自深度比较)。例如,prevState 是对象 { xyz: 1},newState 是具有相同值 { xyz: 1} 的新对象。就像 Class 组件中的 shouldComponentUpdate 生命周期一样,您可以在其中执行任何自定义比较,然后返回条件以重新渲染。
  • @QuocVanTang 我添加了更多示例和解释。看看有没有帮助!
  • 详细信息。也许另一个原因是,在使用 useState 钩子时,我们还将状态分解为单独的较小状态(没有组合成像 Class 组件这样的大状态对象)。所以控制状态变化是显而易见的
【解决方案2】:

补充一下 cbdev420 所说的,这里是一个小抄的链接,它很好地解释了如何使用大多数钩子,如果它可以帮助你https://react-hooks-cheatsheet.surge.sh/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-16
    • 1970-01-01
    • 1970-01-01
    • 2019-08-26
    • 1970-01-01
    • 2022-01-16
    • 2021-10-20
    • 2014-10-03
    相关资源
    最近更新 更多