React函数式组件渲染顺序探究(Demo)

发布时间 2023-07-24 18:17:58作者: milliele

参考资料:
React渲染顺序及useEffect执行顺序探究(含并发模式)

code

import { useEffect, useState } from "react";
import { Button } from "antd";

function Component({ name, children }) {
    const [state, setState] = useState(() => {
        console.log(`Component ${name} initialize state`);
        return 0;
    });

    console.log(`Component ${name} body`);

    useEffect(() => {
        console.log(`Component ${name} all effect`);

        return () => {
            console.log(`Component ${name} all clean`);
        };
    });

    useEffect(() => {
        console.log(`Component ${name} empty effect`);

        return () => {
            console.log(`Component ${name} empty clean`);
        };
    }, []);

    useEffect(() => {
        console.log(`Component ${name} state effect`);

        return () => {
            console.log(`Component ${name} state clean`);
        };
    }, [state]);

    return (
        <div>
            Component {name}: {state}{" "}
            <Button onClick={() => setState(state + 1)}>Add</Button>
            {children}
        </div>
    );
}

function Component2({ name }) {
    const [state, setState] = useState(() => {
        console.log(`Component ${name} initialize state`);
        return 0;
    });

    console.log(`Component ${name} body`);

    useEffect(() => {
        console.log(`Component ${name} all effect`);

        return () => {
            console.log(`Component ${name} all clean`);
        };
    });

    useEffect(() => {
        console.log(`Component ${name} empty effect`);

        return () => {
            console.log(`Component ${name} empty clean`);
        };
    }, []);

    useEffect(() => {
        console.log(`Component ${name} state effect`);

        return () => {
            console.log(`Component ${name} state clean`);
        };
    }, [state]);

    return (
        <div>
            Component {name}: {state}{" "}
            <Button onClick={() => setState(state + 1)}>Add</Button>
            <Component name={`${name}-1 (${state})`}/>
            <Component name={`${name}-2 (${state})`}/>
        </div>
    );
}

function App() {
    console.log(`Parent body`);

    const [state, setState] = useState(() => {
        console.log(`Parent initialize state`);
        return 0;
    });

    useEffect(() => {
        console.log(`Parent empty effect`);

        return () => {
            console.log(`Parent empty clean`);
        };
    }, []);

    useEffect(() => {
        console.log(`Parent state effect`);

        return () => {
            console.log(`Parent state clean`);
        };
    }, [state]);

    useEffect(() => {
        console.log(`Parent all effect`);

        return () => {
            console.log(`Parent all clean`);
        };
    });

    const compo1 = (<Component name={`child1(${state})`}>
        <Component name={`child1-1(${state})`}/>
        <Component name={`child1-2(${state})`}/>
    </Component>);
    const compo2 = <Component2 name={`child2`}/>;
    const components = state % 2 === 0 ? [compo1, compo2] : [compo2, compo1];

    if (state % 3 !== 0) {
        components.push(<Component name={`child3`}/>);
    }

    return (
        <div>
            Parent: {state} <Button onClick={() => setState(state + 1)}>Add</Button>
            {components}
        </div>
    );
}

export default App;

result(React 16)

初始渲染:

# 递归执行函数体
Parent body 
Parent initialize state 
Component child1(0) initialize state 
Component child1(0) body 
Component child1-1(0) initialize state 
Component child1-1(0) body 
Component child1-2(0) initialize state 
Component child1-2(0) body 
Component child2 initialize state 
Component child2 body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
# 递归执行useEffect,同一个component有多个useEffect时,按它在代码中顺序执行
Component child1-1(0) all effect 
Component child1-1(0) empty effect 
Component child1-1(0) state effect 
Component child1-2(0) all effect 
Component child1-2(0) empty effect 
Component child1-2(0) state effect 
Component child1(0) all effect 
Component child1(0) empty effect 
Component child1(0) state effect 
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Parent empty effect 
Parent state effect 
Parent all effect

child1-1的按钮,修改其state:

# 只有依赖项的useEffect被调用
Component child1-1(0) body 
Component child1-1(0) all clean 
Component child1-1(0) state clean 
Component child1-1(0) all effect 
Component child1-1(0) state effect

child1的按钮,修改其state:

# 只有child1被重新渲染,因为child1的孩子是作为children传入的,不会被child1的state影响
Component child1(0) body 
Component child1(0) all clean 
Component child1(0) state clean 
Component child1(0) all effect 
Component child1(0) state effect

child2的按钮,修改其state:

# child2重新渲染,导致child2-1和child2-2也重新渲染
# 先调用body
Component child2 body 
Component child2-1 (1) body 
Component child2-2 (1) body
# 调用clean
# 由于child2-1和child2-2的state都没变,不会调用多余的clean和useEffect
Component child2-1 (0) all clean 
Component child2-2 (0) all clean 
Component child2 all clean 
Component child2 state clean 
# 调用effect
Component child2-1 (1) all effect 
Component child2-2 (1) all effect 
Component child2 all effect 
Component child2 state effect

点Parent的按钮,修改其state,从而导致(1)child1和2交换顺序(2)child3出现

# 依次调用body,【注意!child1和child2重新mount了!】
Parent body 
Component child2 initialize state 
Component child2 body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
Component child1(1) initialize state 
Component child1(1) body 
Component child1-1(1) initialize state 
Component child1-1(1) body 
Component child1-2(1) initialize state 
Component child1-2(1) body 
Component child3 initialize state 
Component child3 body 
# clean函数
# 【注意!由于相当于老的child1和child2消失,所以此处还会调用它们的clean函数】
# 可以看到,child1带的state,是Parent state的旧值0
Component child1(0) all clean 
Component child1(0) empty clean 
Component child1(0) state clean 
Component child1-1(0) all clean 
Component child1-1(0) empty clean 
Component child1-1(0) state clean 
Component child1-2(0) all clean 
Component child1-2(0) empty clean 
Component child1-2(0) state clean 
Component child2 all clean 
Component child2 empty clean 
Component child2 state clean 
# 而child2-1和2-2带的是child2的state的旧值
# all在state更新后重新调用过,所以是最新的旧值1
Component child2-1 (1) all clean 
# 而其余2个是更老的旧值,这是因为useEffect虽然用了name做依赖,但并没有把它列成依赖项
# 所以它们只会取最后一次定义这个函数时的值
Component child2-1 (0) empty clean 
Component child2-1 (0) state clean 
Component child2-2 (1) all clean 
Component child2-2 (0) empty clean 
Component child2-2 (0) state clean 
Parent state clean 
Parent all clean 
# effect函数
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Component child1-1(1) all effect 
Component child1-1(1) empty effect 
Component child1-1(1) state effect 
Component child1-2(1) all effect 
Component child1-2(1) empty effect 
Component child1-2(1) state effect 
Component child1(1) all effect 
Component child1(1) empty effect 
Component child1(1) state effect 
Component child3 all effect 
Component child3 empty effect 
Component child3 state effect 
Parent state effect 
Parent all effect

把每个child都上状态之后,再点Parent按钮:



# Body,child1和2还是重新mount了
Parent body 
Component child1(2) initialize state 
Component child1(2) body 
Component child1-1(2) initialize state 
Component child1-1(2) body 
Component child1-2(2) initialize state 
Component child1-2(2) body 
Component child2 initialize state 
Component child2 body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
Component child3 body
# Clean
Component child2 all clean 
Component child2 empty clean 
Component child2 state clean 
Component child2-1 (1) all clean 
Component child2-1 (0) empty clean 
Component child2-1 (1) state clean 
Component child2-2 (1) all clean 
Component child2-2 (0) empty clean 
Component child2-2 (1) state clean 
Component child1(1) all clean 
Component child1(1) empty clean 
Component child1(1) state clean 
Component child1-1(1) all clean 
Component child1-1(1) empty clean 
Component child1-1(1) state clean 
Component child1-2(1) all clean 
Component child1-2(1) empty clean 
Component child1-2(1) state clean 
Component child3 all clean 
Parent state clean 
Parent all clean 
# effect
# 由于child3是重新渲染,所以只叫了一个effect
Component child1-1(2) all effect 
Component child1-1(2) empty effect 
Component child1-1(2) state effect 
Component child1-2(2) all effect 
Component child1-2(2) empty effect 
Component child1-2(2) state effect 
Component child1(2) all effect 
Component child1(2) empty effect 
Component child1(2) state effect 
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Component child3 all effect 
Parent state effect 
Parent all effect
  • 如果不希望重新mount

    可以把child1和child2加上key

    # body
    Parent body 
    Component child1(2) body 
    Component child1-1(2) body 
    Component child1-2(2) body 
    Component child2 body 
    Component child2-1 (1) body 
    Component child2-2 (1) body 
    Component child3 body 
    # clean,是按effect的后序DFS
    Component child1-1(1) all clean 
    Component child1-2(1) all clean 
    Component child1(1) all clean 
    Component child2-1 (1) all clean 
    Component child2-2 (1) all clean 
    Component child2 all clean 
    Component child3 all clean 
    Parent state clean 
    Parent all clean 
    Component child1-1(2) all effect 
    Component child1-2(2) all effect 
    Component child1(2) all effect 
    Component child2-1 (1) all effect 
    Component child2-2 (1) all effect 
    Component child2 all effect 
    Component child3 all effect 
    Parent state effect 
    Parent all effect 
    

继续上状态,继续点Parent:


# Body
Parent body 
Component child2 initialize state 
Component child2 body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
Component child1(3) initialize state 
Component child1(3) body 
Component child1-1(3) initialize state 
Component child1-1(3) body 
Component child1-2(3) initialize state 
Component child1-2(3) body 
# Clean
# 由于child3 unmount,所有clean都被调用
Component child1(2) all clean 
Component child1(2) empty clean 
Component child1(2) state clean 
Component child1-1(2) all clean 
Component child1-1(2) empty clean 
Component child1-1(2) state clean 
Component child1-2(2) all clean 
Component child1-2(2) empty clean 
Component child1-2(2) state clean 
Component child2 all clean 
Component child2 empty clean 
Component child2 state clean 
Component child2-1 (1) all clean 
Component child2-1 (0) empty clean 
Component child2-1 (1) state clean 
Component child2-2 (1) all clean 
Component child2-2 (0) empty clean 
Component child2-2 (1) state clean 
Component child3 all clean 
Component child3 empty clean 
Component child3 state clean 
Parent state clean 
Parent all clean 
# Effect
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Component child1-1(3) all effect 
Component child1-1(3) empty effect 
Component child1-1(3) state effect 
Component child1-2(3) all effect 
Component child1-2(3) empty effect 
Component child1-2(3) state effect 
Component child1(3) all effect 
Component child1(3) empty effect 
Component child1(3) state effect 
Parent state effect 
Parent all effect

再点Parent:

# BODY
...
Component child3 initialize state 
Component child3 body 
# Clean
...
# effect
...
Component child3 all effect 
Component child3 empty effect 
Component child3 state effect 
Parent state effect 
Parent all effect

result(React 18)

初始渲染:

# 递归执行函数体,可以看到每个body都被执行了2次!并且都是mount!
Parent body 
Parent initialize state 
Parent body 
Parent initialize state 
Component child1(0) initialize state 
Component child1(0) body 
Component child1(0) initialize state 
Component child1(0) body 
Component child1-1(0) initialize state 
Component child1-1(0) body 
Component child1-1(0) initialize state 
Component child1-1(0) body 
Component child1-2(0) initialize state 
Component child1-2(0) body 
Component child1-2(0) initialize state 
Component child1-2(0) body 
Component child2 initialize state 
Component child2 body 
Component child2 initialize state 
Component child2 body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
# 递归执行useEffect,同一个component有多个useEffect时,按它在代码中顺序执行
Component child1-1(0) all effect 
Component child1-1(0) empty effect 
Component child1-1(0) state effect 
Component child1-2(0) all effect 
Component child1-2(0) empty effect 
Component child1-2(0) state effect 
Component child1(0) all effect 
Component child1(0) empty effect 
Component child1(0) state effect 
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Parent empty effect 
Parent state effect 
Parent all effect 
# Clean
# Effect
Component child1-1(0) all clean 
Component child1-1(0) empty clean 
Component child1-1(0) state clean 
Component child1-2(0) all clean 
Component child1-2(0) empty clean 
Component child1-2(0) state clean 
Component child1(0) all clean 
Component child1(0) empty clean 
Component child1(0) state clean 
Component child2-1 (0) all clean 
Component child2-1 (0) empty clean 
Component child2-1 (0) state clean 
Component child2-2 (0) all clean 
Component child2-2 (0) empty clean 
Component child2-2 (0) state clean 
Component child2 all clean 
Component child2 empty clean 
Component child2 state clean 
Parent empty clean 
Parent state clean 
Parent all clean 
Component child1-1(0) all effect 
Component child1-1(0) empty effect 
Component child1-1(0) state effect 
Component child1-2(0) all effect 
Component child1-2(0) empty effect 
Component child1-2(0) state effect 
Component child1(0) all effect 
Component child1(0) empty effect 
Component child1(0) state effect 
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Parent empty effect 
Parent state effect 
Parent all effect 

child1-1的按钮,修改其state:与React 16一致

child1的按钮,修改其state::与React 16一致

child2的按钮,修改其state:

# child2重新渲染,导致child2-1和child2-2也重新渲染
# 先调用body,调用了2次
Component child2 body * 2
Component child2-1 (1) body * 2
Component child2-2 (1) body * 2
# 调用clean
# 由于child2-1和child2-2的state都没变,不会调用多余的clean和useEffect
Component child2-1 (0) all clean 
Component child2-2 (0) all clean 
Component child2 all clean 
Component child2 state clean 
# 调用effect
Component child2-1 (1) all effect 
Component child2-2 (1) all effect 
Component child2 all effect 
Component child2 state effect 

点Parent的按钮,修改其state,从而导致(1)child1和2交换顺序(2)child3出现

# 依次调用body,【注意!child1和child2重新mount了!】
Parent body * 2
Component child2 initialize state 
Component child2 body 
Component child2 initialize state 
Component child2 body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
Component child1(1) initialize state 
Component child1(1) body 
Component child1(1) initialize state 
Component child1(1) body 
Component child1-1(1) initialize state 
Component child1-1(1) body 
Component child1-1(1) initialize state 
Component child1-1(1) body 
Component child1-2(1) initialize state 
Component child1-2(1) body 
Component child1-2(1) initialize state 
Component child1-2(1) body 
Component child3 initialize state 
Component child3 body 
Component child3 initialize state 
Component child3 body 
# clean函数
# 【注意!由于相当于老的child1和child2消失,所以此处还会调用它们的clean函数】
# 可以看到,child1带的state,是Parent state的旧值0
Component child1(0) all clean 
Component child1(0) empty clean 
Component child1(0) state clean 
Component child1-1(0) all clean 
Component child1-1(0) empty clean 
Component child1-1(0) state clean 
Component child1-2(0) all clean 
Component child1-2(0) empty clean 
Component child1-2(0) state clean 
Component child2 all clean 
Component child2 empty clean 
Component child2 state clean 
Component child2-1 (1) all clean 
Component child2-1 (0) empty clean 
Component child2-1 (0) state clean 
Component child2-2 (1) all clean 
Component child2-2 (0) empty clean 
Component child2-2 (0) state clean 
Parent state clean 
Parent all clean 
# effect函数
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Component child1-1(1) all effect 
Component child1-1(1) empty effect 
Component child1-1(1) state effect 
Component child1-2(1) all effect 
Component child1-2(1) empty effect 
Component child1-2(1) state effect 
Component child1(1) all effect 
Component child1(1) empty effect 
Component child1(1) state effect 
Component child3 all effect 
Component child3 empty effect 
Component child3 state effect 
Parent state effect 
Parent all effect 
# 第二次clean,相当于新mount的组件都有第二次clean和effect
Component child2-1 (0) all clean 
Component child2-1 (0) empty clean 
Component child2-1 (0) state clean 
Component child2-2 (0) all clean 
Component child2-2 (0) empty clean 
Component child2-2 (0) state clean 
Component child2 all clean 
Component child2 empty clean 
Component child2 state clean 
Component child1-1(1) all clean 
Component child1-1(1) empty clean 
Component child1-1(1) state clean 
Component child1-2(1) all clean 
Component child1-2(1) empty clean 
Component child1-2(1) state clean 
Component child1(1) all clean 
Component child1(1) empty clean 
Component child1(1) state clean 
Component child3 all clean 
Component child3 empty clean 
Component child3 state clean 
# 第二次effect
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Component child1-1(1) all effect 
Component child1-1(1) empty effect 
Component child1-1(1) state effect 
Component child1-2(1) all effect 
Component child1-2(1) empty effect 
Component child1-2(1) state effect 
Component child1(1) all effect 
Component child1(1) empty effect 
Component child1(1) state effect 
Component child3 all effect 
Component child3 empty effect 
Component child3 state effect

把每个child都上状态之后,再点Parent按钮:

# Body,都叫2遍,并且child1和2还是重新mount,child3只update
Parent body * 2
Component child1(2) initialize state 
Component child1(2) body 
Component child1(2) initialize state 
Component child1(2) body 
Component child1-1(2) initialize state 
Component child1-1(2) body 
Component child1-1(2) initialize state 
Component child1-1(2) body 
Component child1-2(2) initialize state 
Component child1-2(2) body 
Component child1-2(2) initialize state 
Component child1-2(2) body 
Component child2 initialize state 
Component child2 body 
Component child2 initialize state 
Component child2 body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-1 (0) initialize state 
Component child2-1 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
Component child2-2 (0) initialize state 
Component child2-2 (0) body 
Component child3 body * 2
# clean - unmount
Component child2 all clean 
Component child2 empty clean 
Component child2 state clean 
Component child2-1 (1) all clean 
Component child2-1 (0) empty clean 
Component child2-1 (1) state clean 
Component child2-2 (1) all clean 
Component child2-2 (0) empty clean 
Component child2-2 (1) state clean 
Component child1(1) all clean 
Component child1(1) empty clean 
Component child1(1) state clean 
Component child1-1(1) all clean 
Component child1-1(1) empty clean 
Component child1-1(1) state clean 
Component child1-2(1) all clean 
Component child1-2(1) empty clean 
Component child1-2(1) state clean 
# clean - update
Component child3 all clean 
Parent state clean 
Parent all clean
# effect 
Component child1-1(2) all effect 
Component child1-1(2) empty effect 
Component child1-1(2) state effect 
Component child1-2(2) all effect 
Component child1-2(2) empty effect 
Component child1-2(2) state effect 
Component child1(2) all effect 
Component child1(2) empty effect 
Component child1(2) state effect 
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect 
Component child3 all effect 
Parent state effect 
Parent all effect 
# mount的组件的第二次clean
Component child1-1(2) all clean 
Component child1-1(2) empty clean 
Component child1-1(2) state clean 
Component child1-2(2) all clean 
Component child1-2(2) empty clean 
Component child1-2(2) state clean 
Component child1(2) all clean 
Component child1(2) empty clean 
Component child1(2) state clean 
Component child2-1 (0) all clean 
Component child2-1 (0) empty clean 
Component child2-1 (0) state clean 
Component child2-2 (0) all clean 
Component child2-2 (0) empty clean 
Component child2-2 (0) state clean 
Component child2 all clean 
Component child2 empty clean 
Component child2 state clean 
Component child1-1(2) all effect 
Component child1-1(2) empty effect 
Component child1-1(2) state effect 
Component child1-2(2) all effect 
Component child1-2(2) empty effect 
Component child1-2(2) state effect 
Component child1(2) all effect 
Component child1(2) empty effect 
Component child1(2) state effect 
Component child2-1 (0) all effect 
Component child2-1 (0) empty effect 
Component child2-1 (0) state effect 
Component child2-2 (0) all effect 
Component child2-2 (0) empty effect 
Component child2-2 (0) state effect 
Component child2 all effect 
Component child2 empty effect 
Component child2 state effect