修复不是 re-rendering 的 child 组件的方法(由于作为道具而不是状态传递的数据发生变化)?

Ways to fix a child component that is not re-rendering (due to change in data being passed in as props, not state)?

背景

我正在开发一个使用 ReactJS 作为渲染库的 Meteor 应用程序。

目前,我在更新数据时遇到 Child 组件 re-render 的问题,即使 parent 正在访问更新的数据并据称将其传递下去到 child.

Parent 组件是 Table 数据。 Child 组件是一个 Click-to-edit 日期字段。

它(理论上)的工作方式:Parent 组件将日期的现有数据作为道具传递给 child 组件。 Child 组件获取现有的道具数据,处理它并使用它设置一些状态,然后有 2 个选项:

我在 table 中每行两次使用 child 组件,每次使用它时,都需要访问数据库中的最新日期数据。因此,如果一个字段中的数据发生更改,第二个字段应反映该更改。

问题

第二个字段 反映数据库中的更改,除非我手动刷新页面并强制 child 组件使用新数据呈现。编辑的字段 反映数据更改,因为它反映了状态中存储的内容。

阅读 React 文档后,我确定问题是因为日期作为道具进入,然后作为状态处理——并且因为组件不会 re-render 从道具更换。

我的问题

我该怎么做才能解决这个问题?

我对文档的所有阅读都强烈建议远离 forceUpdate() 和 getDerivedStateFromProps() 之类的东西,但结果我不确定如何让数据通过这样的方式传递我想要它。

想法?

我的代码

我稍微缩短了代码并删除了特定于我的项目的变量名,但如果有帮助,我可以提供更多实际的变量名。我认为我的问题比直接调试更概念化。

Parent

ParentComponent () {
    //Date comes as a prop from database subscription
    var date1 = this.props.dates.date1 
    var date2 = this.props.dates.date2
    return(
        <ChildComponent 
            id='handle-date-1'
            selected={[date1, date2]} />
        <ChildComponent 
            id='handle-date-2'
            selected={[date1, date2]} />
    )
}

Child

ChildComponent() {
    constructor(props) {
        super(props);
        this.state = {
            date1: this.props.selected[0],
            date2: this.props.selected[1],
            field: false,
        };
    }

    handleDatePick() {
        //Handles the event listeners for clicks in/out of the div, handles calling Meteor to update database.
    }
    renderDate1() {
        return(
            <div>
                {this.state.field == false &&
                    <p onClick={this.handleClick}>{formatDate(this.state.date1)}</p>}
                {this.state.field == true &&
                    <DatePicker
                        selected={this.state.date1}
                        startDate={this.state.date1}
                        onChange={this.handleDatePick}
                    />
                }
            </div>
        )
    }
    renderDate2() {
        return(
            <div>
                {this.state.field == false &&
                    <p onClick={this.handleClick}>{formatDate(this.state.date2)}</p>}
                {this.state.field == true &&
                    <DatePicker
                        selected={this.state.date2}
                        startDate={this.state.date2}
                        onChange={this.handleDatePick}
                    />
                }
            </div>
        )
    }
    render() {
        return(
            //conditionally calls renderDate1 OR renderDate2
        )
    }
}

(如果这个 code/my 的解释很粗糙,那是因为我还是一个相当 beginner/low 水平的开发人员。我没有接受过正规培训,所以我在工作中学习,同时正在开发一个非常困难的应用程序。作为一个单独的开发者。这是一个很长的故事。请温柔点!)

React 文档有一节介绍 constructor() 的最佳实践用法。阅读这篇文章,密切关注 yellow-highlighted "Note" 部分,应该可以阐明您正在 运行 遇到的确切问题。

本质上,constructor()只是运行一次,最常用于初始化 internal/local状态(或绑定方法)。这意味着你的 child 组件设置 date1date2 使用你的 selected prop once when constructor() 被称为 child。 selected 在调用 constructor() 时的任何值都将设置为 child 的状态,并且即使值继续更改也将保持不变。

因此,传递给 child 组件的 selected 道具的任何连续更新 将不会 反映在该组件的内部状态中,这意味着将没有 re-render 该组件。您需要在 child 组件的其他地方使用 React 的 setState() 方法来正确更新 child 的状态并触发 re-render.

使用 React 生命周期方法的组合来正确更新您的 child 组件是可行的方法。下面的代码片段让您大致了解 componentDidMount()componentDidUpdate()

中的实施要更改的内容

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      date1: 0,
      date2: 0,
    };
  }

  /*
    Any time the child mounts to the DOM, 
    you can use the method below to set 
    your state using the current value of your 
    selected prop from the parent component...
  */

  componentDidMount() {
    this.setState({
      date1: this.props.selected[0],
      date2: this.props.selected[1]
    });
  }

  /* 
   When the child receives an update from its parent, 
   in this case a new date selection, and should 
   re-render based on that update, use the method below 
   to make a comparison of your selected prop and then 
   call setState again...
  */

  componentDidUpdate(prevProps) {
    if (prevProps.selected !== this.props.selected) {
      this.setState({
        date1: this.props.selected[0],
        date2: this.props.selected[1]
      });
    }
  }

  render() {
    const { date1, date2 } = this.state;
    return (
      <div style={{ border: `4px dotted red`, margin: 8, padding: 4 }}>
        <h1 style={{ fontWeight: 'bold' }}>Child</h1>
        <h2>The value of date1 is {date1}</h2>
        <h2>The value of date2 is {date2}</h2>
      </div>
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      valOne: 0,
      valTwo: 0
    };
  }

  incrementOne = () => {
    this.setState(prevState => ({ valOne: (prevState.valOne += 1) }));
  };

  incrementTwo = () => {
    this.setState(prevState => ({ valTwo: (prevState.valTwo += 1) }));
  };

  render() {
    const { valOne, valTwo } = this.state;
    return (
      <div style={{ border: `4px solid blue`, margin: 8, padding: 4 }}>
        <h1 style={{ fontWeight: 'bold', fontSize: 18 }}>Parent</h1>
        <button onClick={() => this.incrementOne()}>Increment date1</button>
        <button onClick={() => this.incrementTwo()}>Increment date2</button>
        <Child selected={[valOne, valTwo]} />
      </div>
    );
  }
}

ReactDOM.render(<Parent />, document.querySelector('#app'));
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

由于您正在为您的 children 使用 React 组件模式,因此充分利用 React 生命周期方法将对您有所帮助。我强烈建议您学习 the React component lifecyle. As you continue to use React, it will become essential in cases such as this. componentDidMount() and componentDidUpdate() 是很好的起点。

希望这对您有所帮助。让我们知道结果如何。