В каком порядке отображаются родительские и дочерние компоненты?
Если у меня есть два компонента (родитель и потомок), как это:
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");
}
}
Порядок
- оказывать
- наконец заблокировать
- использоватьLayoutEffect
- использованиеЭффект
- очистка useLayoutEffect
- использовать эффект очистки
Это показывает данный тест.
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://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>
);
}