componentWillReceiveProps,React Hook的componentDidUpdate

时间:2019-02-23 16:35:15

标签: javascript reactjs react-hooks

我遇到了两个挑战:

  • 即使根据React准则,不鼓励派生状态,但某些边缘情况仍然需要它。
    对于带有React Hook的功能组件,如果我确实需要派生状态,那么与React Hook等效的实现是什么?在类component中,将在每个父级渲染器的componentWillReceiveProps中更新

请参见下面的代码示例:

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;

  • 至于componentDidUpdate,当使用React Hook时,componentDidUpdate就是它的合作伙伴,它是

    React.useEffect(()=> {       return()=> {

      };
    }, [parentProp]);
    

    useEffect的第二个参数可确保仅在更改道具时才执行代码,但是如果我要根据多个道具的不同来执行各自的任务,该怎么办? 如何通过useEffect 完成它?

请参见下面的代码示例:

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;

9 个答案:

答案 0 :(得分:3)

可以使用useEffect钩子完成相当于旧componentWillReceive道具的react钩子,只需指定我们要侦听依赖项数组中的更改的道具即可。

类似的东西:

export default (props) => {


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


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

对于componentDidUpdate,只需省略依赖项数组,则每次重新渲染后都会调用useEffect函数。

答案 1 :(得分:2)

您可以使用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>

在更改单独的道具时执行单独效果的最简单方法是创建多个useEffect挂钩,这些挂钩仅在其中一个单独的道具发生更改时才运行。

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>

答案 2 :(得分:1)

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

import React from 'react';

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

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

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

此处演示:https://codesandbox.io/embed/qzn8y9y24j?fontsize=14

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

如果您确实需要使用单独的状态,则可以使用类似的内容

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>;
};

答案 3 :(得分:1)

setCount将使用useEffect触发重新渲染,并且依赖项数组[count]将指定当count的值不同时,仅对count更新进行“监视”。 因此,可以以某种方式替换componentWillReceiveProps。 我们可以说“ Each Render Has Its Own Props and State”。如果要根据道具更改触发重新渲染,则可以具有多个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]) 

答案 4 :(得分:1)

只需使用这样的useEffect。

useEffect( () => {
    props.actions.fetchSinglePost(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.

答案 5 :(得分:1)

如果在组件顶部使用useMemo挂钩,并使它依赖于所有道具,则它将在每次道具更改之前运行。 useEffect在更新的渲染之后触发,并且由于依赖于所有道具,因此会在重新渲染之后根据所有道具触发。

const Component = (...props) => {
   // useState, useReducer if have
   useMemo(() => {
     // componentWillReceiveProps
   },[...props]);
   // ...other logic and stuff
   useEffect(() => {
     // componentDidUpdate
   }, [...props]);
};

答案 6 :(得分:1)

在某种情况下,您想要复制一个日志系统,例如 why did you render,并且您需要 nextProps 在这里帮助钩子。

  • 与旧的 lifeCycle 函数对齐,将内容重命名为 props 和 nextProps
  • nextProps 表示当前道具
  • props 表示之前的道具
const useComponentWillReceiveProps(nextProps, callback) {
  const props = useRef(nextProps) 

  useEffect(() => {
    callback(nextProps, props.current)
    props.current = nextProps
  })
}

用法

const diffLogger = (nextProps, props, { name = "Component" } = {}) => {
  Object.keys(nextProps)
    .filter((key) => nextProps[key] !== props[key])
    .forEach((key) => {
      console.log(
        `%c${name}:%c${key}`,
        "color:#ff5722; font-size:1rem; font-weight:bold;",
        "color:#ffc107; font-size:1.2rem; font-weight:bold;",
        {
          from: props[key],
          to: nextProps[key],
        }
      )
    })
}
const Button = (props) => {
 useComponentWillReceiveProps(props, diffLogger, {name: 'Button'})
 return <button onClick={props.onClick}>Button</button>
}

现在如果您的按钮重新呈现,例如通过 onClick 新参考,您将得到如下内容: enter image description here

答案 7 :(得分:0)

我意识到您的“派生状态”示例在故意上很简单,但是由于派生状态的合法案例很少,因此很难提出替代建议,除非是逐案处理,因为它取决于您使用派生状态的原因。在您提供的特定示例中,没有理由在类情况下使用派生状态,因此在挂钩情况下也没有理由(值可以在本地派生而无需将其置于状态)。如果派生的值昂贵,则可以使用Tholle提出的useMemo。如果这些都不适合您考虑的更实际的情况,则需要提供一个更具体的情况,该情况确实需要派生状态。

就您的componentDidUpdate示例而言,如果您要为不同道具做的事情是独立的,则可以为每个道具使用单独的效果(即多个useEffect调用)。如果您想精确地进行示例操作(即,如果companyName也没有按照您的{{1}指示进行更改,则仅对groupName进行更改}),那么您可以将refs用于更复杂的条件。渲染期间,您不应该对参考进行变异(一旦支持并发模式,总是有可能丢弃渲染/重做渲染),因此该示例使用最后一种效果对参考进行更新。在我的示例中,我使用ref来避免在初始渲染上进行效果工作(请参阅this related question中的Tholle的回答),并在根据以下决定决定是否进行工作时检测else if是否发生了变化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);

Edit Derived state and componentDidUpdate

答案 8 :(得分:0)

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

Derived state for Hooks =有条件地设置状态,并在渲染阶段直接设置

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

useEffect相比,此更新状态无需附加提交阶段。 React Strict Mode支持上述模式(无警告):

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>

注释1 componentWillReceiveProps已被弃用一段时间。 getDerivedStateFromProps是类组件的派生状态的继承者。

注释2 Check preferred solutions,然后再求助于派生状态。


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

您可以完全不使用useEffect部门,或者最好添加另一个道具部门:

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