Javascript React Hook 的 componentWillReceiveProps、componentDidUpdate

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/54843675/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:14:52  来源:igfitidea点击:

componentWillReceiveProps, componentDidUpdate for React Hook

javascriptreactjsreact-hooks

提问by Deng Zhebin

I run into two challenges:

我遇到了两个挑战:

  • Even if, as per React guideline, derived stateis discouraged, but some edge cases still need it.
    In terms of a functional component with React Hook, What is the equivalent implementation with React Hook, If I do need derived state ? which in class component, will be updated in componentWillReceiveProps on every parent render
  • 即使根据 React 指南,不鼓励派生状态,但某些边缘情况仍然需要它。
    就带有 React Hook 的功能组件而言,React Hook的等效实现是什么,如果我确实需要派生状态?在类组件中,将在每个父渲染的 componentWillReceiveProps 中更新

see below code sample:

见下面的代码示例:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: props.count > 100 ? 100 : props.count,
    }

  }

  /*What is the equivalent implementation when React Hook is used here componentWillReceiveProps*/
  componentWillReceiveProps(nextProps) {
    if (nextProps.count !== this.props.count) {
      this.setState({
        count: nextProps.count > 100 ? 100 : nextProps.count
      });
    }
  }

  render() {
    return ( <
      div > {
        this.state.count
      } <
      /div>
    );
  }
}

export default App;

  • As for componentDidUpdate, componentDidUpdate has it's couterpart when React Hook is used ,you have to use it like,

    React.useEffect(() => {
      return () => {
    
       };
    }, [parentProp]);
    

    the Second param for useEffect makes sure code is executed only when prop changes, but what if I want to do respective tasks based on multiple respective props changes? how to get it done with useEffect?

  • 至于componentDidUpdate,componentDidUpdate在使用React Hook时有它的对应部分,你必须像这样使用它,

    React.useEffect(() => {
      return () => {
    
       };
    }, [parentProp]);
    

    useEffect 的第二个参数确保代码仅在 prop 更改时执行,但是如果我想根据多个相应的 props 更改执行相应的任务怎么办?如何使用 useEffect完成它?

see below code sample:

见下面的代码示例:

class App extends Component {


  /*What is the equivalent implementation when functional component with React Hook is used here */
  componentDidUpdate(prevProps, prevState) {
    if (prevProps.groupName !== this.props.groupName) {
      console.log('Let'
        's say, I do want to do some task here only when groupName differs');
    } else if (prevProps.companyName !== this.props.companyName) {
      console.log('Let'
        's say,I do want to do some different task here only when companyName differs');
    }

  }


  render() {
    /*for simplicity, render code is ignored*/
    return null;
  }
}

export default App;

回答by fgonzalez

The react hook equivalent to the old componentWillReceive props can be done using the useEffect hook, just specifying the prop that we want to listen for changes in the dependency array.

相当于旧的 componentWillReceive 道具的 react 钩子可以使用 useEffect 钩子来完成,只需指定我们要监听依赖数组中的变化的道具。

I.e:

IE:

export default (props) => {


    useEffect( () => {
        console.log('counter updated');
    }, [props.counter])


    return <div>Hi {props.counter}</div>
}

For componentDidUpdate just by omitting the dependency array, the useEffect function will be called after every rerender.

对于仅通过省略依赖数组的 componentDidUpdate,每次重新渲染后都会调用 useEffect 函数。

I.e:

IE:

export default (props) => {

导出默认值(道具)=> {

useEffect( () => {
    console.log('counter updated');
})


return <div>Hi {props.counter}</div>

}

}

回答by Tholle

You can use the useMemohook to store a calculation and put props.countin the array given as second argument to recalculate the value when it changes.

您可以使用useMemo钩子来存储计算并放入props.count作为第二个参数给出的数组,以便在值发生变化时重新计算该值。

const { useState, useEffect, useMemo } = React;

function App() {
  const [count, setCount] = useState(50);

  useEffect(() => {
    setTimeout(() => {
      setCount(150);
    }, 2000);
  }, []);

  return <DisplayCount count={count} />;
}

function DisplayCount(props) {
  const count = useMemo(() => props.count > 100 ? 100 : props.count, [props.count]);

  return <div> {count} </div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>

The easiest way to do separate effects when separate props change is to create multiple useEffecthooks that are only run when one of the separate props change.

当单独的 props 改变时,做单独的效果最简单的方法是创建多个useEffect钩子,这些钩子只在单独的 props 改变时运行。

const { useState, useEffect } = React;

function App() {
  const [groupName, setGroupName] = useState('foo');
  const [companyName, setCompanyName] = useState('foo');

  useEffect(() => {
    setTimeout(() => {
      setGroupName('bar');
    }, 1000);
    setTimeout(() => {
      setCompanyName('bar');
    }, 2000);
  }, []);

  return <DisplayGroupCompany groupName={groupName} companyName={companyName} />;
}

function DisplayGroupCompany(props) {
  useEffect(() => {
    console.log("Let's say, I do want to do some task here only when groupName differs");
  }, [props.groupName])
  useEffect(() => {
    console.log("Let's say,I do want to do some different task here only when companyName differs");
  }, [props.companyName])

  return <div> {props.groupName} - {props.companyName} </div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>

回答by Thien Ly

setCount will trigger rerender, using useEffect and the dependencies array [count] will specify that only "watch" on count's update when count's value is different. So this can be somehow replaced componentWillReceiveProps. We can say that "Each Render Has Its Own Props and State". If you want to trigger rerender depends on props change, you can have several useEffect then.

setCount 将触发重新渲染,使用 useEffect 并且依赖项数组 [count] 将指定当 count 的值不同时仅“观察”count 的更新。所以这可以以某种方式替换componentWillReceiveProps。我们可以说“每个渲染器都有自己的道具和状态”。如果你想触发 rerender 依赖 props 变化,你可以有几个 useEffect 那么。

/*componentWillReceiveProps*/
  useEffect(() => {
       count > 100 ? setCount(100) : setCount(count)
    }, [count]) 

  useEffect(() => {
        console.log(`Let's say, I do want to do some task here only when groupName differs`);
    }, [groupName])
    useEffect(() => {
        console.log(`Let''s say,I do want to do some different task here only when companyName differs`);
    }, [companyName]) 

回答by Kevin F.

In your scenario, you don't need to use or re-implement getDerivedStateFromPropsat all. You just need to create a new variable to get the new form of data. Using state in this scenario will just cause another re-rendering which is not good performance wise.

在您的场景中,您根本不需要使用或重新实现getDerivedStateFromProps。您只需要创建一个新变量即可获取新形式的数据。在这种情况下使用 state 只会导致另一次重新渲染,这在性能上不是很好。

import React from 'react';

const App = ({ count }) => {
  const derivedCount = count > 100 ? 100 : count;

  return (
    <div>Counter: {derivedCount}</div>
  );
}

App.propTypes = {
  count: PropTypes.number.isRequired
}

Demo here: https://codesandbox.io/embed/qzn8y9y24j?fontsize=14

演示在这里:https: //codesandbox.io/embed/qzn8y9y24j?fontsize=14

You can read more on different ways to solve these kind of scenarios without using getDerivedStateFromPropshere: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

您可以在不使用getDerivedStateFromProps这里的情况下阅读有关解决此类场景的不同方法的更多 信息:https: //reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

If you really need to use a separate state, you can use something like this

如果你真的需要使用一个单独的状态,你可以使用这样的东西

import React, { useState } from 'react';

const App = ({ count }) => {
  const [derivedCounter, setDerivedCounter] = useState(
    count > 100 ? 100 : count
  );

  useEffect(() => {
    setDerivedCounter(count > 100 ? 100 : count);
  }, [count]); // this line will tell react only trigger if count was changed

  return <div>Counter: {derivedCounter}</div>;
};

回答by Hasan Zahran

simply by using useEffect like this.

只需像这样使用 useEffect 即可。

useEffect( () => {
    props.actions.fetchSignlePost(props.match.params.id); > I'm dispatching an action here.
}, [props.comments]) > and here to watch comments and call the action in case there is any change.

回答by ford04

1.) What is the equivalent implementation with React Hook, If I do need derived state?

1.) React Hook 的等效实现是什么,如果我确实需要派生状态?

Derived statefor Hooks = set state conditionally and directlyin the render phase:

Hooks 的派生状态=在渲染阶段有条件地直接设置状态:

constComp = (props) => {
  const [derivedState, setDerivedState] = useState(42);
  if (someCondition) {
    setDerivedState(...);
  }
  // ...
}

This updates state without an additional commit phase as opposed to useEffect. Above pattern is supported by React Strict Mode(no warnings):

useEffect. 以上模式由React Strict 模式支持(无警告):

const App = () => {
  const [row, setRow] = React.useState(1);

  React.useEffect(() => {
    setTimeout(() => {
      setRow(2);
    }, 3000);
  }, []);

  return (
    <React.StrictMode>
      <Comp row={row} />
    </React.StrictMode>
  );
}

const Comp = ({ row }) => {
  const [isScrollingDown, setIsScrollingDown] = React.useState(false);
  const [prevRow, setPrevRow] = React.useState(null);

  console.log("render, prevRow:", prevRow)

  if (row !== prevRow) {
    console.log("derive state");
    // Row changed since last render. Update isScrollingDown.
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js" integrity="sha256-4gJGEx/zXAxofkLPGXiU2IJHqSOmYV33Ru0zw0TeJ30=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.min.js" integrity="sha256-9xBa2Hcuh2S3iew36CzJawq7T9iviOAcAVz3bw8h3Lo=" crossorigin="anonymous"></script>
<div id="root"></div>

Note 1: componentWillReceivePropshas been deprecated for quite some time. getDerivedStateFromPropsis the class components' successor in terms of derived state.

注 1componentWillReceiveProps已被弃用一段时间。getDerivedStateFromProps就派生状态而言,是类组件的后继者。

Note 2: Check preferred solutionsbefore you resort to derived state.

注 2:在使用派生状态之前检查首选解决方案



2.) What if I want to do respective tasks based on multiple respective props changes?

2.) 如果我想根据多个各自的道具变化来做各自的任务怎么办?

You can either leave useEffectdeps out completely or preferably add another prop dep:

您可以useEffect完全不使用deps,也可以最好添加另一个 prop dep:

React.useEffect(() => {
  return () => { };
}, [parentProp, secondProp]);

回答by Ryan Cogswell

I realize your "derived state" example is intentionally simple, but because there are so few legitimate cases of derived state, it is difficult to make a recommendation on the replacement except on a case-by-case basis since it depends on the reason you are using derived state. In the particular example you provided, there was no reason to use derived state in the class case and so there continues to be no reason in the hook case (the value can just be derived locally without putting it in state). If the derived value is expensive, you can use useMemoas Tholle presents. If these don't fit the more realistic case(s) you have in mind, you would need to present a more specific case that truly requires derived state.

我意识到您的“派生状态”示例是故意简单的,但是由于派生状态的合法情况很少,因此除了逐案之外,很难对替换提出建议,因为这取决于您的原因正在使用派生状态。在您提供的特定示例中,没有理由在类情况下使用派生状态,因此在钩子情况下仍然没有理由(该值可以仅在本地派生而不将其置于状态)。如果派生值昂贵,您可以useMemo作为 Tholle 礼物使用。如果这些不适合您想到的更现实的情况,您将需要提出一个真正需要派生状态的更具体的情况。

As far as your componentDidUpdateexample, if what you want to do for the different props is independent, then you can use separate effects for each (i.e. multiple useEffectcalls). If you want to do exactlywhat is in your example (i.e. only do something for a companyNamechange if groupNamedidn't also change as indicated by your else if), then you can use refsfor more sophisticated conditions. You should notmutate the ref during rendering (there is always the possibility of the render being discarded/redone once concurrent mode is supported), so the example uses the last effect to make updates to the refs. In my example, I use a ref to avoid doing effect work on the initial render (see Tholle's answer in this related question) and to detect whether or not groupNamechanged when deciding whether or not to do work based on a companyNamechange.

就您的componentDidUpdate示例而言,如果您要为不同的道具做的事情是独立的,那么您可以为每个道具使用单独的效果(即多次useEffect调用)。如果您想完全按照您的示例进行操作(即,companyName如果groupName您的 指示没有更改,则仅对更改执行某些操作else if),那么您可以将refs用于更复杂的条件。你应该不是在渲染过程中发生变异裁判(总有渲染被丢弃/恢复的可能性一旦支持的并发模式),这样的例子使用最后的效果进行更新,裁判。在我的示例中,我使用 ref 来避免对初始渲染进行效果工作(请参阅 Tholle 在此相关问题中的回答) 并在根据groupName更改决定是否执行工作时检测是否companyName更改。

const { useState, useEffect, useRef } = React;

const DerivedStateFromProps = ({ count }) => {
  const derivedCount = count > 100 ? 100 : count;

  return (
    <div>
      Derived from {count}: {derivedCount}{" "}
    </div>
  );
};
const ComponentDidUpdate = ({ groupName, companyName }) => {
  const initialRender = useRef(true);
  const lastGroupName = useRef(groupName);
  useEffect(
    () => {
      if (!initialRender.current) {
        console.log("Do something when groupName changes", groupName);
      }
    },
    [groupName]
  );
  useEffect(
    () => {
      if (!initialRender.current) {
        console.log("Do something when companyName changes", companyName);
      }
    },
    [companyName]
  );
  useEffect(
    () => {
      if (!initialRender.current && groupName === lastGroupName.current)
        console.log(
          "Do something when companyName changes only if groupName didn't also change",
          companyName
        );
    },
    [companyName]
  );
  useEffect(
    () => {
      // This effect is last so that these refs can be read accurately in all the other effects.
      initialRender.current = false;
      lastGroupName.current = groupName;
    },
    [groupName]
  );

  return null;
};
function App() {
  const [count, setCount] = useState(98);
  const [groupName, setGroupName] = useState("initial groupName");
  const [companyName, setCompanyName] = useState("initial companyName");
  return (
    <div>
      <div>
        <DerivedStateFromProps count={count} />
        <button onClick={() => setCount(prevCount => prevCount + 1)}>
          Increment Count
        </button>
      </div>
      <div>
        <ComponentDidUpdate groupName={groupName} companyName={companyName} />
        groupName:{" "}
        <input
          type="text"
          value={groupName}
          onChange={event => setGroupName(event.target.value)}
        />
        <br />
        companyName:{" "}
        <input
          type="text"
          value={companyName}
          onChange={event => setCompanyName(event.target.value)}
        />
        <br />
        change both{" "}
        <input
          type="text"
          onChange={event => {
            const suffix = event.target.value;
            setGroupName(prev => prev + suffix);
            setCompanyName(prev => prev + suffix);
          }}
        />
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

Edit Derived state and componentDidUpdate

编辑派生状态和 componentDidUpdate