简体   繁体   English

为什么 getDerivedStateFromProps 不允许使用 state 更新重新渲染? componentWillReceiveProps 不是问题 - ReactJS

[英]Why getDerivedStateFromProps does not allow to re-render with the state update? Not a problem for componentWillReceiveProps - ReactJS

I'm trying to move from componentWillReceiveProps to getDerivedStateFromProps and in some cases, I was successful but when the case is append the props to the existing state, then things start to not behaving the same way.我正在尝试从 componentWillReceiveProps 转移到 getDerivedStateFromProps,在某些情况下,我成功了,但是当案例是 append 时,现有 state 的道具开始表现不同。 When a make an update to the component's state, the state changes (and the component did after updated) but still renders the previous state.当对组件的 state 进行更新时,state 会发生变化(并且组件在更新后也会发生变化),但仍会呈现以前的 state。 Something weird happens when using getDerivedStateFromProp instead of componentWillReceiveProps.使用 getDerivedStateFromProp 而不是 componentWillReceiveProps 时会发生一些奇怪的事情。 It seems that method does not handle well with 'internal' changes.似乎该方法不能很好地处理“内部”更改。 In the following example, I have getDerivedStateFromProp on Child and it works, but because is only rendering the props.在下面的示例中,我在 Child 上有 getDerivedStateFromProp 并且它可以工作,但因为只是渲染道具。 This behavior was also observed in a simpler example where I didn't have any child components and was just rendering state changes.在一个更简单的示例中也观察到了这种行为,其中我没有任何子组件并且只是呈现 state 更改。

The code below shows a child component that is used to print/show the data received by props while uses a delete data handler (to remove data that is stored at Parent from child component interaction).下面的代码显示了一个子组件,用于打印/显示 props 接收到的数据,同时使用删除数据处理程序(从子组件交互中删除存储在 Parent 的数据)。 When using getDerivedStateFromProps() I can't access to this.state and the prevState doesn't mean the same since the state is accumulative.使用getDerivedStateFromProps()时,我无法访问this.state和 prevState 并不意味着相同,因为 state 是累积的。 And when I remove data from the child component, doesn't update the props of the child (while using componentWillReceiveProps was OK).当我从子组件中删除数据时,不会更新子组件的道具(使用 componentWillReceiveProps 时可以)。 So, I do not find a way to substitute my UNSAFE_componentWillReceiveProps所以,我找不到替代我的UNSAFE_componentWillReceiveProps的方法

componentWillReceiveProps:组件WillReceiveProps:


  UNSAFE_componentWillReceiveProps(nextProps){
    this.setState({
      data: [...this.state.data,...nextProps.data]
    })
  }

getDerivedStateFromProps: getDerivedStateFromProps:

   static getDerivedStateFromProps(nextProps,state) {

    if (!isEqual(nextProps.data, state.data)) {
      return {
        data: [...state.data, ...nextProps.data]
      };
    }
    return null;
  }


The original code that works as intended (before getDerivedStateFromProps on Parent Comp.)按预期工作的原始代码(在 Parent Comp 上的 getDerivedStateFromProps 之前。)

DataConsole - Parent Component: DataConsole - 父组件:

export class DataConsole extends Component {
  // Used for unsubscribing when our components unmount
  unsub = null;

  static defaultProps = {
    data: [],
  };

  constructor(props) {
    super(props);
    this.state = {
      data: [],
    };

    this.handleTableRowClick = this.handleTableRowClick.bind(this);
  }


   UNSAFE_componentWillReceiveProps(nextProps){
    this.setState({
      data: [...this.state.data,...nextProps.data]
    })
  }

  handleTableRowClick(key) {
    console.log(
      "handleTable",
      key,
      this.state.data[key],
      this.state.data.length
    );
     const e = this.state.data.splice(key, 1)
     //console.log("remove?", e , this.state.data.length)

    this.setState({
      undoDataRemove: e
    });
  }


  render() {
    return (
      <div>
        <Container
          fluid
          style={{ paddingLeft: 0, paddingRight: 0 }}
          className="DataContainer"
        >
          <Row noGutters>
            <Col sm={8} className="ConsoleTable">
              <div>
                <DataViewer
                  data={this.state.data}
                  rowClickHandler={this.handleTableRowClick}
                />
              </div>

       ...




DataViewer - Child Component DataViewer - 子组件

import isEqual from "react-fast-compare";
...

export class DataViewer extends Component {
  static defaultProps = {
    data: [],
  };

  constructor(props){
    super(props)
    this.state={data: []}
  }


  componentDidUpdate() {
    console.log("DataViewer updated");
  }

  static getDerivedStateFromProps(nextProps, prevProps) {

    console.log(nextProps, prevProps)
    if (!isEqual(nextProps.data, prevProps.data)) {
      return {
        data: nextProps.data
      };
    }
    return null;
  }


  render() {
    return (
      <div className={"TableData"}>
        <Table responsive="lg" striped borderless hover>
          <tbody>
            {this.state.data.map((elem, ids) => {
              if (!isEmpty(elem)) {
                return (
                  <tr key={ids} onClick={() => this.props.rowClickHandler(ids)}>
                    <td>{ids + 1}</td>
                    {Object.keys(elem).map(function (value, idx) {
                      return (
                        <td key={idx}>
                          {value}:{elem[value]}
                        </td>
                      );
                    })}
                  </tr>
                );
              } else {
                return null;
              }
            })}
          </tbody>
        </Table>
      </div>
    );
  }
}


There is a bug in your code that causes your problem, and it is unrelated to getDerivedStateFromProps and UNSAFE_componentWillReceiveProps .您的代码中有一个错误导致您的问题,它与getDerivedStateFromPropsUNSAFE_componentWillReceiveProps无关。

The faulty line is this:故障线是这样的:

const e = this.state.data.splice(key, 1) 

It changes this.state.data without calling setState .它更改this.state.data而不调用setState Never do that.永远不要那样做。 The only way you are ever allowed to make any changes to this.state is via this.setState or by returning something to be merged into state from getDerivedStateFromProps , not any other way.唯一允许您对this.state进行任何更改的方法是通过this.setState或从 getDerivedStateFromProps 返回要合并到getDerivedStateFromProps的内容,而不是任何其他方式。

In order to more easily cope with changes deep in your state tree, immutable libraries come in handily.为了更轻松地应对 state 树的深层变化,不可变库可以派上用场。 immer is currently among the top candidates in this category. immer目前是该类别中的顶级候选人之一。 Using immer , you can modify state any way you want, as long as you wrap it into a produce call using the pattern this.setState(produce(this.state, newState => { /* change newState here */ })) :使用immer ,您可以随意修改state ,只要您使用模式this.setState(produce(this.state, newState => { /* change newState here */ }))将其包装到produce调用中:

import produce from 'immer';

// ...

this.setState(produce(this.state, newState => {
  const e = newState.data.splice(key, 1);
  newState.undoDataRemove = e;
}));

Hope, this can help.希望,这可以提供帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM