componentWillReceiveProps,React Hook的componentDidUpdate

问题描述 投票:2回答:3

我遇到了两个挑战:

  • 即使根据React指南,也不鼓励派生状态,但是一些边缘情况仍然需要它。 就React Hook的功能组件而言,React Hook的等效实现是什么,如果我确实需要派生状态?在类组件中,将在每个父渲染的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有它的couterpart,它是 React.useEffect(()=> {return()=> { }; }, [parentProp]); useEffect的第二个参数确保代码仅在prop改变时执行,但是如果我想根据多个各自的道具差异执行各自的任务呢?如何用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;
javascript reactjs react-hooks
3个回答
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>

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

0
投票

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

至于你的componentDidUpdate例子,如果你想为不同的道具做什么是独立的,那么你可以为每个道具使用单独的效果(即多个useEffect调用)。如果你想要完全按照你的例子做什么(即如果你的companyName表明,如果groupName也没有改变的话,只做else if改变的事情),那么你可以使用refs来获得更复杂的条件。你不应该在渲染过程中改变ref(在支持并发模式时总是有可能丢弃/重做渲染),因此该示例使用最后一个效果来对ref进行更新。在我的例子中,我使用ref来避免对初始渲染进行效果工作(参见Tholle在this related question中的答案),并在决定是否根据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

© www.soinside.com 2019 - 2024. All rights reserved.