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

What is the correct pattern in ReactJS lifecycle (v. 16.4) to pass data

ReactJS 生命周期 (v. 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;

然后我的子组件应该从 props 中获取 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 中)然后返回,table 细胞不见了。这可能是因为我错误地实施了生命周期,或者是因为我的密钥有问题...我不知道。

更新:

这是我的路由代码:

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

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

React 知道你的 child 组件的 props 已经改变,并且会 re-render 为你的组件。您不需要在 child 中使用任何生命周期方法或状态。只需在 child 的渲染方法 中从道具 重新生成 rowscolumns 并立即使用它们。

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

Child 可以知道是否更换道具。如果没有,则阻止渲染 child 直到数据准备好。在这种情况下,componentDidUpdate 使 child 知道 prop 的变化。没关系,也可以在更新时调用 componentDidMount 中的 this.createTableData();(未在初始渲染时调用)。

注意:componentDidMount只会调用一次。

如果您仍然有问题,那么首先 console.log 在 parent 渲染中检查它是否被调用了两次以及传递了什么。 child 渲染相同。

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

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

为了让子props正确初始化(这样你的子组件就可以在componentDidMount()中正确使用它们),只有当你的parent state被设置时你才应该挂载子组件。

以下是使用异步初始状态渲染组件的方法:

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