React函数式组件渲染顺序探究(Demo)
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