【问题标题】:shouldComponentUpdate equivalent for functional component, to ignore state changesshouldComponentUpdate 等效于功能组件,以忽略状态更改
【发布时间】:2020-07-12 07:21:08
【问题描述】:

我的代码有一个组件,它接受两个 props 并有自己的内部状态。
组件应仅在其道具更改时重新渲染。状态更改不应触发重新渲染。
此行为可以通过基于类的组件和自定义 shouldComponentUpdate 函数来实现。
但是,这将是代码库中第一个基于类的组件。一切都是通过功能组件和钩子完成的。 因此,我想知道是否可以使用功能组件编写所需的功能。

在几个没有解决真正问题的答案之后,我想我必须重新提出我的问题。这是一个包含两个组件的最小示例:

  • Inner 接受一个 prop 并具有状态。这是有问题的组件。状态更改后不得重新渲染。道具更改应触发重新渲染。
  • 外部是内部的包装。它在这个问题的范围内没有任何意义,只是为了给 Inner 提供道具并模拟道具更改。

为了演示所需的功能,我使用基于类的组件实现了 Inner。 A live version of this code can be found on codesandbox。如何将其迁移到功能组件:

Inner.tsx:

import React, { Component } from 'react'

interface InnerProps{outerNum:number}
interface InnerState{innerNum:number}

export default class Inner extends Component<InnerProps, InnerState> {
    state = {innerNum:0};

    shouldComponentUpdate(nextProps:InnerProps, nextState:InnerState){
        return this.props != nextProps;
    }
    render() {
        return (
            <button onClick={()=>{
                this.setState({innerNum: Math.floor(Math.random()*10)})
            }}>
                {`${this.props.outerNum}, ${this.state.innerNum}`}
            </button>
        )
    }
}

外部.tsx:

import React, { useState } from "react";
import Inner from "./Inner";

export default function Outer() {
  const [outerState, setOuterState] = useState(1);

  return (
    <>
      <button
        onClick={() => {
          setOuterState(Math.floor(Math.random() * 10));
        }}
      >
        change outer state
      </button>
      <Inner outerNum={outerState}></Inner>
    </>
  );
}

The official docs say 将组件包装在React.memo 中。但这似乎不适用于防止状态更改的重新渲染。它仅适用于道具更改。

我试图让React.memo 工作。你可以看到一个代码版本,其中 Outer 和 Inner 都是功能组件here

相关问题:

How to use shouldComponentUpdate with React Hooks? :这个问题只涉及道具更改。接受的答案建议使用React.memo

shouldComponentUpdate in function components:这个问题早于有状态的功能组件。接受的答案解释了功能组件如何不需要shouldComponentUpdate,因为它们是无状态的。

【问题讨论】:

  • React.memo:“这个方法只作为性能优化存在。不要依赖它来“阻止”渲染,因为这会导致错误。” - 你能向我们介绍你可能需要它的情况吗?也许我们可以建议其他解决方案,例如引入key 属性。但如果没有案例本身,就很难推断出问题所在。

标签: javascript reactjs react-hooks reactive-programming


【解决方案1】:

React memo 不会停止状态变化

React.memo 仅检查 prop 更改。如果你的函数组件 包裹在 React.memo 里面有一个 useState 或者 useContext Hook 实现,它仍然会在状态或上下文发生变化时重新渲染。

参考:-https://reactjs.org/docs/react-api.html#reactmemo

【讨论】:

    【解决方案2】:

    您的Inner 组件依赖于Outer 组件的num 属性,you can't prevent 在属性更改时渲染它,因为React.memo 进行属性比较:

    // The default behaviour is shallow comparison between previous and current render properties.
    const areEqual = (a, b) => a.num === b.num;
    export default React.memo(Inner, areEqual);
    

    通过记忆 Inner 组件并删除 num 依赖项,它不会在 Outer 渲染时渲染,请参阅附加的沙箱。

    export default function Outer() {
      const [outerState, setOuterState] = useState(1);
    
      return (
        <>
          ...
        // v Inner is memoized and won't render on `outerState` change.
          <Inner />
        </>
      );
    }
    


    如果你想用钩子实现shouldComponentUpdate,你可以试试:

    const [currState] = useState();
    // shouldUpdateState your's custom function to compare and decide if update state needed
    setState(prevState => {
      if(shouldUpdateState(prevState,currState)) {
        return currState;
      }
      return prevState;
    });
    

    【讨论】:

    • 不,重新渲染道具更改是可以的。当内部状态发生变化时,我想防止重新渲染。
    • 当内部状态改变时父级不会渲染,你想防止什么?内部渲染?
    • 是的,我想阻止内部组件的重新渲染,在调用 setstate 之后,也在内部组件上。
    • 你看到沙盒了吗?内部组件在外部 setState 上不渲染。
    • 如果你不想让内部渲染所以不要调用 set state :\
    【解决方案3】:

    React 的设计是由 setState -> 重新渲染循环驱动的。 props 的变化实际上是父组件中某处的 setState。如果您不希望 setState 触发重新渲染,那么为什么首先要使用它呢?

    您可以使用const state = useRef({}).current 来存储您的内部状态。

    function InnerFunc(props) {
      const state = useRef({ innerNum: 0 }).current;
      return (
        <button
          onClick={() => {
            state.innerNum = Math.floor(Math.random() * 10);
          }}
        >
          {`${props.outerNum}, ${state.innerNum}`}
        </button>
      );
    }
    

    也就是说,这仍然是一个有效的问题:“如何以反应钩子方式实现 shouldComponentUpdate?”这是解决方案:

    function shouldComponentUpdate(elements, predicate, deps) {
      const store = useRef({ deps: [], elements }).current
      const shouldUpdate = predicate(store.deps)
      if (shouldUpdate) {
        store.elements = elements
      }
      store.deps = deps
      return store.elements
    }
    
    // Usage:
    
    function InnerFunc(props) {
      const [state, setState] = useState({ innerNum: 0 })
      const elements = (
        <button
          onClick={() => {
            setState({ innerNum: Math.floor(Math.random() * 10) });
          }}
        >
          {`${props.outerNum}, ${state.innerNum}`}
        </button>
      );
    
      return shouldComponentUpdate(elements, (prevDeps) => {
        return prevDeps[0] !== props
      }, [props, state])
    }
    

    请注意,在调用setState 时无法阻止重新渲染循环,上面的钩子只是确保重新渲染的结果与之前的渲染结果保持一致。

    【讨论】:

      【解决方案4】:

      你应该在setState之前在函数中使用提供浏览器和捕获的事件,像这样

      function setState = (e) =>{ //the e is the event that give you the browser
      //changing the state
      e.preventDefault();
      }
      

      【讨论】:

        猜你喜欢
        • 2011-11-18
        • 2021-11-02
        • 2021-06-20
        • 2022-06-27
        • 2021-03-19
        • 2021-08-17
        • 2020-12-04
        • 2021-03-23
        • 2020-05-12
        相关资源
        最近更新 更多