Какой правильный шаблон в жизненном цикле ReactJS (v. 16.4) для передачи данных - PullRequest
0 голосов
/ 05 сентября 2018

Каков правильный шаблон в жизненном цикле 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;

Мой дочерний компонент должен затем взять 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, и я не знаю, правильно ли это.

Вторая проблема, с которой я сталкиваюсь, заключается в том, что она отображается нормально при первом посещении страницы, но если я перехожу на другую страницу (в реакции-маршрутизатора) и затем возвращаюсь, данные в ячейках таблицы исчезают. Это может быть связано либо с неправильной реализацией жизненного цикла, либо с тем, что что-то не так с моими ключами ... Я не знаю.

UPDATE:

Это мой код маршрутизации:

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 голосов
/ 05 сентября 2018

Ваш родительский компонент в порядке, но вы должны помнить, что setState является асинхронным. Первый вызов render пропустит пустой объект. Это распространенная проблема при рендеринге выбранных данных. setState обновляет состояние и принудительное повторное рендеринг - секунда render с обновленными данными.

Ребенок может знать о смене реквизита или нет. Если нет, то запретите рендеринг потомка, пока данные не будут готовы. В этом случае componentDidUpdate информирует ребенка об изменениях в опоре. Это нормально и нормально вызывать this.createTableData(); в componentDidMount (не вызывается при первоначальном рендеринге) при обновлении.

Примечание: componentDidMount будет вызываться только один раз.

Если у вас все еще есть проблемы, то сначала console.log в родительском рендере, чтобы проверить, вызывается ли он дважды и что передается. То же самое для рендера ребенка.

Вы можете использовать shouldComponentUpdate вместо componentDidUpdatecreateTableData просто вызывается из render). Подготовка данных необходима как метод оптимизации - когда не все данные изменены.

Вы можете вставлять логи везде, чтобы отслеживать, что, когда и аргументы (данные) жизненных циклов.

0 голосов
/ 05 сентября 2018

Дочерний компонент следует монтировать только в том случае, если установлено родительское состояние, чтобы правильно инициализировать дочерние реквизиты (тогда ваш дочерний компонент может их правильно использовать в 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>
    );
  }
}
0 голосов
/ 05 сентября 2018

React знает, что реквизиты вашего дочернего компонента изменились, и выполнит повторную визуализацию для вас. Вам не нужно использовать какие-либо методы жизненного цикла или состояния в ребенке. Просто восстановите rows и columns из реквизита внутри метода рендеринга ребенка и сразу используйте их.

...