В каком порядке отображаются родительские и дочерние компоненты?

Если у меня есть два компонента (родитель и потомок), как это:

1-родитель (обратный отсчет):

var Countdown = React.createClass({
  getInitialState: function(){
    return{count: 0};
  },
  handleSetCountdown: function(seconds){
    this.setState({
      count: seconds
    });
  },
  render:function(){
    var {count} = this.state;
    return(
      <div>
        <Clock totalSeconds={count}/>
        <CountdownForm onSetCountdown={this.handleSetCountdown} />
      </div>
    );
  }
});

module.exports =Countdown;

2-Дитя (CountdownForm):

var CountdownForm = React.createClass({
  onSubmit: function(e){
    e.preventDefault();
    var strSeconds = this.refs.seconds.value;
    if(strSeconds.match(/^[0-9]*$/)){
      this.refs.seconds.value ='';
      this.props.onSetCountdown(parseInt(strSeconds,10));

    }
  },
  render: function(){
    return(
      <div>
        <form ref="form" onSubmit={this.onSubmit} className="countdown-form">
          <input type="text" ref="seconds" placeholder="Enter Time In Seconds"/>
          <button className="button expanded">Start</button>
        </form>
      </div>
    );
  }
});

module.exports = CountdownForm;

Я запутался в жизненном цикле (порядок, в котором компоненты отображаются).

6 ответов

Решение

Я не сразу вижу четкое "это порядок событий жизненного цикла между родителем и потомком" в документах React, хотя я мог его пропустить.

Конечно, эмпирически определить тривиально:

class Child extends React.Component {
    constructor(...args) {
        super(...args);
        console.log("Child constructor");
    }
    componentWillMount(...args) {
        console.log("Child componentWillMount");
    }
    componentDidMount(...args) {
        console.log("Child componentDidMount");
    }
    render() {
        console.log("Child render");
        return <div>Hi there</div>;
    }
}

class Parent extends React.Component {
    constructor(...args) {
        super(...args);
        console.log("Parent constructor");
    }
    componentWillMount(...args) {
        console.log("Parent componentWillMount");
    }
    componentDidMount(...args) {
        console.log("Parent componentDidMount");
    }
    render() {
        console.log("Parent render start");
        const c = <Child />;
        console.log("Parent render end");
        return c;
    }
}

ReactDOM.render(<Parent />, document.getElementById("react"));
.as-console-wrapper {
  max-height: 100% !important;
}
<div id="react"></div>
<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>

Это показывает нам порядок:

Родительский конструктор
Родительский компонент WillMount
Родительский рендер начало
Конец родительского рендера
Детский конструктор
Дочерний компонент WillMount
Рендеринг ребенка
Дочерний компонент DidMount
Родительский компонент DidMount

Что заставило меня задуматься о порядке детей внутри родителя, так что:

class Child extends React.Component {
    constructor(props, ...rest) {
        super(props, ...rest);
        console.log(this.props.name + " constructor");
    }
    componentWillMount(...args) {
        console.log(this.props.name + " componentWillMount");
    }
    componentDidMount(...args) {
        console.log(this.props.name + " componentDidMount");
    }
    render() {
        console.log(this.props.name + " render");
        return <div>Hi from {this.props.name}!</div>;
    }
}

class Parent extends React.Component {
    constructor(...args) {
        super(...args);
        console.log("Parent constructor");
    }
    componentWillMount(...args) {
        console.log("Parent componentWillMount");
    }
    componentDidMount(...args) {
        console.log("Parent componentDidMount");
    }
    render() {
        console.log("Parent render start");
        const result =
            <div>
                <Child name="Child1" />
                <Child name="Child2" />
            </div>;
        console.log("Parent render end");
        return result;
    }
}

ReactDOM.render(<Parent />, document.getElementById("react"));
.as-console-wrapper {
  max-height: 100% !important;
}
<div id="react"></div>
<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>

Что дает нам:

Родительский конструктор
Родительский компонент WillMount
Родительский рендер начало
Конец родительского рендера
Child1 конструктор
Child1 componentWillMount
Child1 render
Child2 конструктор
Child2 componentWillMount
Child2 render
Child1 componentDidMount
Child2 componentDidMount
Родительский компонент DidMount

Совсем не удивительно, но хорошо, чтобы перепроверить.:-)

Просто добавляем componentWillUnmount в цикл:

class Child extends React.Component {
    constructor(props, ...rest) {
        super(props, ...rest);
        console.log(this.props.name + " constructor");
    }
    componentWillMount(...args) {
        console.log(this.props.name + " componentWillMount");
    }
    componentWillUnmount(...args) {
        console.log(this.props.name + " componentWillUnmount");
    }
    componentDidMount(...args) {
        console.log(this.props.name + " componentDidMount");
    }
    render() {
        console.log(this.props.name + " render");
        return <div>Hi from {this.props.name}!</div>;
    }
}

class Parent extends React.Component {
    constructor(...args) {
        super(...args);
        console.log("Parent constructor");
    }
    componentWillMount(...args) {
        console.log("Parent componentWillMount");
    }
    componentWillUnmount(...args) {
        console.log("Parent componentWillUnmount");
    }
    componentDidMount(...args) {
        console.log("Parent componentDidMount");
    }
    render() {
        console.log("Parent render start");
        const result =
            <div>
                <Child name="Child1" />
                <Child name="Child2" />
            </div>;
        console.log("Parent render end");
        return result;
    }
}

class ParentWrapper extends React.Component {
    constructor(...args) {
        super(...args);
        this.state = { showParent: true };
        setTimeout(() => { this.setState({ showParent: false }) });
    }
    render() {
        return <div>{this.state.showParent ? <Parent /> : ''}</div>;
    }
}

ReactDOM.render(<ParentWrapper />, document.getElementById("react"));
.as-console-wrapper {
  max-height: 100% !important;
}
<div id="react"></div>
<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>

результат:

Parent constructor
Parent componentWillMount
Parent render start
Parent render end
Child1 constructor
Child1 componentWillMount
Child1 render
Child2 constructor
Child2 componentWillMount
Child2 render
Child1 componentDidMount
Child2 componentDidMount
Parent componentDidMount
Parent componentWillUnmount
Child1 componentWillUnmount
Child2 componentWillUnmount

С такими функциональными компонентами, как этот...

        const MyComponent = () => {
    useEffect(() => {
      trackingFn("useEffect MyComponent");
      subscriptions.push(() => { trackingFn("subscription MyComponent") })
      return () => trackingFn("useEffect cleanup MyComponent");
    })
    useLayoutEffect(() => {
      trackingFn("useLayoutEffect MyComponent");
      return () => trackingFn("useLayoutEffect cleanup MyComponent");
    })
    try {
      trackingFn("render MyComponent");
      return <MyChildComponent />
    } finally {
      trackingFn("finally MyComponent");
    }
  }

Порядок

  1. оказывать
  2. наконец заблокировать
  3. использоватьLayoutEffect
  4. использованиеЭффект
  5. очистка useLayoutEffect
  6. использовать эффект очистки

Это показывает данный тест.

      import { act, render } from "@testing-library/react";
import { useEffect, useLayoutEffect } from "react";

it("should call the methods in the expected order", async () => {

  const trackingFn = jest.fn();
  const subscriptions: (() => void)[] = [];

  const MyGrandChildComponent = () => {
    useEffect(() => {
      trackingFn("useEffect MyGrandChildComponent");
      subscriptions.push(() => { trackingFn("subscription MyGrandChildComponent") })
      return () => trackingFn("useEffect cleanup MyGrandChildComponent");
    })
    useLayoutEffect(() => {
      trackingFn("useLayoutEffect MyGrandChildComponent");
      return () => trackingFn("useLayoutEffect cleanup MyGrandChildComponent");
    })
    try {
      trackingFn("render MyGrandChildComponent");
      return <div />
    } finally {
      trackingFn("finally MyGrandChildComponent");
    }
  }

  const MyChildComponent = () => {
    useEffect(() => {
      trackingFn("useEffect MyChildComponent");
      subscriptions.push(() => { trackingFn("subscription MyChildComponent") })
      return () => trackingFn("useEffect cleanup MyChildComponent");
    })
    useLayoutEffect(() => {
      trackingFn("useLayoutEffect MyChildComponent");
      return () => trackingFn("useLayoutEffect cleanup MyChildComponent");
    })
    try {
      trackingFn("render MyChildComponent");
      return <MyGrandChildComponent />
    } finally {
      trackingFn("finally MyChildComponent");
    }
  }

  const MyComponent = () => {
    useEffect(() => {
      trackingFn("useEffect MyComponent");
      subscriptions.push(() => { trackingFn("subscription MyComponent") })
      return () => trackingFn("useEffect cleanup MyComponent");
    })
    useLayoutEffect(() => {
      trackingFn("useLayoutEffect MyComponent");
      return () => trackingFn("useLayoutEffect cleanup MyComponent");
    })
    try {
      trackingFn("render MyComponent");
      return <MyChildComponent />
    } finally {
      trackingFn("finally MyComponent");
    }
  }

  const { unmount } = render(<MyComponent />);

  await act(() => Promise.resolve());
  subscriptions.forEach(f => f());
  unmount();
  expect(trackingFn.mock.calls).toEqual([
    ['render MyComponent'],
    ['finally MyComponent'],
    ['render MyChildComponent'],
    ['finally MyChildComponent'],
    ['render MyGrandChildComponent'],
    ['finally MyGrandChildComponent'],
    ['useLayoutEffect MyGrandChildComponent'],
    ['useLayoutEffect MyChildComponent'],
    ['useLayoutEffect MyComponent'],
    ['useEffect MyGrandChildComponent'],
    ['useEffect MyChildComponent'],
    ['useEffect MyComponent'],
    ['subscription MyGrandChildComponent'],
    ['subscription MyChildComponent'],
    ['subscription MyComponent'],
    ['useLayoutEffect cleanup MyComponent'],
    ['useLayoutEffect cleanup MyChildComponent'],
    ['useLayoutEffect cleanup MyGrandChildComponent'],
    ['useEffect cleanup MyComponent'],
    ['useEffect cleanup MyChildComponent'],
    ['useEffect cleanup MyGrandChildComponent']
  ])
});
``

живая демонстрация

реакция родитель-ребенок-жизненный цикл-порядок

https://33qrr.csb.app/

https://codesandbox.io/s/react-parent-child-lifecycle-order-33qrr

создать заказ


parent constructor
parent WillMount
parent render

child constructor
child WillMount
child render
child DidMount

parent DidMount

уничтожить приказ

parent WillUnmount

child WillUnmount

// child unmount

// parent unmount



Порядок рендеринга выполняется в порядке дерева компонентов реакции, однако порядок монтирования - в обратном порядке (сначала монтируется самый внутренний дочерний компонент).

Можно заставить родительский метод ComponentDidMount выполняться перед дочерним методом ComponentDidMount. А вот как это делается.

Внутри первого div в родительском элементе, прежде чем что-либо будет отображаться, проверьте, установлено ли еще состояние. Отрисовывать что-либо только в том случае, если установлено состояние.

Пример:

      render() {
  const { t } = this.props;
  return (
    <div>
      {this.state.row !== null ? (
            ...
      ) : null}
    </div>
  );
}
Другие вопросы по тегам