ReactJS生命周期(16.4版)中用于传递数据的正确模式是什么

时间:2018-09-05 09:24:28

标签: javascript reactjs lifecycle

ReactJS生命周期(16.4版)中显示父组件中componentDidMount子组件中数据的正确模式是什么?

我有一个应该足够简单的场景,但是它并没有达到我期望的方式。我想将数据从父组件传递到子组件,子组件又将数据转换为可以显示的内容。

我已经阅读了这篇文章https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html,并且正在尝试使模式正确。

以前,我使用componentWillMount来做到这一点,并且效果很好,但是正如我所说的,它在componentDidMount上的表现很奇怪。

我的父组件在其componentDidMount中获取数据,该数据会更新状态,然后将其传递给子组件:

class NewTable extends Component {
    constructor(props) {
        super(props);

        this.state = {
            dataSet: {}
        }
    }

    componentDidMount() {
        const dataSet = DataSet.getColorDataSet();

        this.setState({
            dataSet: dataSet
        });
    }

    render() {

        return (
            <div className="example">
                <h2>HTML Table</h2>
                <NewKSParser.HTMLTable dataSet={this.state.dataSet} id="myid" />
            </div>
        );
    }
}

export default NewTable;

然后我的子组件应该从道具中拾取dataSet并显示它:

export class HTMLTable extends Component {

    constructor(props) {
        super(props);

        this.state = {
            columns: [],
            rows: []
        }
    }

    componentDidUpdate(prevProps, prevState) {
        if (prevProps.dataSet !== this.props.dataSet) {
            this.createTableData();
        }
    }

    createTableData = () => {
        const dataSet = { ...this.props.dataSet };

        const rows = dataSet.dataSets ? dataSet.dataSets[0].rows : [];
        const metaCols = dataSet.dataSets ? dataSet.dataSets[0].metadata.columns : [];
        const columns = this.generateColumns(metaCols);

        this.setState({
            columns: columns,
            rows: rows
        });
    }

    generateColumns = (metaCols) => {
        const columns = metaCols.map((col) => {
            let obj = {};
            obj.id = col.id;
            obj.index = col.index;
            obj.title = col.title;

            return obj;
        });

        return columns;
    };


    render() {

        const { rows, columns } = this.state;

        const tableHeader = columns.map((column) => {
            return <th key={column.id}>{column.title}</th>
        });

        const tableCells = rows.map((row, idx1) => {
            return (<tr key={'row_' + idx1}>
                {row.cells.map((cellContent, idx2) => <td key={`cell_${idx1}_${idx2}`}>{cellContent}</td>)}
            </tr>);
        });

        return (
            <table id={this.props.myid} key={this.props.myid}>
                <thead>
                    <tr>
                        {tableHeader}
                    </tr>
                </thead>
                <tbody>
                    {tableCells}
                </tbody>
            </table>
        );
    }
}

如您所见,我将代码放在componentDidUpdate中,因为将其放在孩子的componentDidMount方法中时根本无法正常工作。

我把它放在componentDidUpdate中似乎很奇怪,我不知道那是否正确。

我遇到的第二个问题是,当我第一次访问该页面时,它可以很好地呈现,但是如果我转到另一个页面(在react-router内)然后又回来,则表单元格中的数据就消失了。可能是因为我实施的生命周期错误,或者是因为我的钥匙有问题...我不知道。

更新:

这是我的路由代码:

class KnowledgeSets extends Component {

    render() {

        return (
            <Router>
                <main>
                    <TopMenu />
                    <Switch>
                        <Route exact path="/" render={()=><Home {...this.props} />} />
                        <Route path="/example" render={()=><Example {...this.props} />} />
                        <Route path="/htmltablecomponent" render={()=><Table {...this.props} />} />
                        <Route path="/complexexpandable" render={()=><ComplexExpandable {...this.props} />} />
                        <Route path="/newdataviewer" render={()=><NewDataViewer {...this.props} />} />
                        <Route path="/newhtmltablecomponent" render={()=><NewTable {...this.props} />} />
                        <Route path="/search" render={()=><Search {...this.props} />} />
                    </Switch>
                </main>
            </Router>
        );
    }
}

export default KnowledgeSets;

如果我将子组件更改为...

componentDidMount() {

    console.log(this.props.dataSet);

    this.createTableData();
}

记录的输出为{}。即使父级的状态已更改,也永远不会更新。

3 个答案:

答案 0 :(得分:1)

React知道您的子组件的道具已更改,并将为您重新渲染该组件。您无需在子级中使用任何生命周期方法或状态。只需从孩子的渲染方法中的道具内部重新生成rowscolumns,并立即使用它们。

答案 1 :(得分:1)

您的父组件正常,但您必须记住setState是异步的。第一次调用render将传递空对象。这是呈现获取的数据时常见的问题。 setState更新状态并强制重新渲染-第二render使用更新的数据。

孩子可以知道是否改变道具。如果没有,则阻止渲染子级,直到数据准备就绪为止。在这种情况下,componentDidUpdate使孩子知道道具的改变。没关系,可以在更新时在this.createTableData();中调用componentDidMount(在初始渲染时不调用)。

注意:componentDidMount仅被调用一次。

如果仍然有问题,则首先在父渲染中console.log检查它是否被调用了两次以及传递了什么。子渲染也一样。

您可以使用shouldComponentUpdate代替componentDidUpdate(以及createTableData只是从渲染调用)。作为优化方法,需要准备数据-当并非所有数据都被更改时。

您可以在任何地方插入日志记录,以跟踪生命周期的内容,时间和参数(数据)。

答案 2 :(得分:1)

只有在设置了父状态时,才应该挂载子组件,以便正确初始化子道具(然后,子组件可以在componentDidMount()中正确使用它们)。

这是如何以异步初始状态呈现组件的方法:

class NewTable extends Component {
  constructor(props) {
    super(props);
    this.state = {
      dataSet: {},
      loading: true
    };
  }

  componentDidMount() {
    const dataSet = DataSet.getColorDataSet();
    this.setState({
      dataSet,
      loading: false
    });
  }

  render() {
    if (this.state.loading) {
      return (<div> Loading </div>)
    }
    return (
      <div className="example">
        <h2>
        HTML Table
        </h2>
        <NewKSParser.HTMLTable dataSet={this.state.dataSet} id="myid" />
      </div>
    );
  }
}