6

背景

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

目前,即使父级正在访问更新的数据并据称将其传递给子级,我也无法在数据更新时重新渲染子级组件。

父组件是一个数据表。子组件是一个点击编辑日期字段。

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

  • 默认值:显示数据
  • 如果用户单击数据字段:更改为输入并允许用户选择日期(使用 react-datepicker),更改状态 - 当用户在字段外单击时,触发返回仅显示并将更新的数据从状态保存到数据库

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

问题

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

阅读 React 文档后,我确信问题在于日期作为道具进入,然后作为状态处理 - 并且因为组件不会从道具更改中重新渲染。

我的问题

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

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

想法?

我的代码

我已经对代码进行了一些缩写并删除了特定于我的项目的变量名,但如果有帮助的话,我可以提供更多的实际值。我认为我的问题比直接调试更具概念性。

家长

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]} />
    )
}

孩子

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

(如果这段代码/我的解释很粗略,那是因为我仍然是一个相当初学者/低级别的开发人员。我没有接受过正规培训,所以我在开发一个非常困难的应用程序时正在学习工作。作为一个独立开发者。说来话长。请温柔!)

4

1 回答 1

4

React 文档有一节介绍constructor(). 阅读该内容,密切关注黄色突出显示的“注意”部分,应该可以阐明您遇到的确切问题。

本质上,constructor()只运行一次,最常用于初始化内部/本地状态(或绑定方法)。这意味着您的子组件正在设置date1,并且在为该子组件调用一次时使用您的道具date2中的值。无论何时调用is的值都将设置为孩子的状态,并且即使该值继续更改也将保持不变。selectedconstructor()selectedconstructor()

selected因此,传递给子组件的 prop 的任何后续更新都不会反映在该组件的内部状态中,这意味着不会重新渲染该组件。您需要setState()在子组件的其他地方使用 React 的方法来正确更新该子组件的状态并触发重新渲染。

使用 React 生命周期方法的组合来正确更新您的子组件是要走的路。下面的代码片段为您提供了有关在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>

由于您正在为您的孩子使用 React 组件模式,因此充分利用 React 生命周期方法将真正帮助您。我不能强烈建议你学习React 组件生命周期。随着您继续使用 React,它在这种情况下将变得必不可少。componentDidMount()并且componentDidUpdate()是开始的好地方。

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

于 2018-10-10T23:54:51.733 回答