今天放假有点空,对React的渲染(render)和hooks的行为作了几个简单的小实验,记录如下。
通过 yarn create react-app test
生成项目脚手架。
const App = () => {
console.log('Rendering B1...');
return (
<div id="app">
</div>
);
};
const C1 = () => {
console.log('Rendering C1...');
return (
<div className="C1">
<div>This is C1!</div>
</div>
);
};
const C2 = () => {
console.log('Rendering C2...');
return (
<div className="C2">
This is C2!
</div>
);
};
const B1 = () => {
const [count, setCount] = useState(0);
console.log('Rendering B1...');
return (
<div className="B1">
<button onClick={() => setCount(count + 1)} >{count}</button>
</div>
);
};
步骤:
结果:
Rendering App...
Rendering C1...
Rendering C2...
结论:
静态无变化,只执行渲染一次
步骤:
结果:
Rendering App...
Rendering B1...
Rendering B1...
Rendering B1...
结论:
对于B1来说初始化执行渲染一次,每点击一次,本身state变化导致渲染一次。
父组件App不受B1变化影响
步骤:
结果:
Rendering App...
Rendering B1...
Rendering C1...
Rendering B1...
Rendering C1...
结论:
对于B1的子组件C1来说初始化执行渲染一次,每点击一次,父组件state变化导致渲染一次。
步骤:
结果:
Rendering App...
Rendering B1...
Rendering C1...
Rendering C2...
Rendering B1...
Rendering C1...
Rendering C2...
结论:
对于B1的孙组件C2来说初始化执行渲染一次,每点击一次,祖父组件state变化导致渲染一次。
步骤:
结果:
Rendering App...
Rendering B1...
Rendering C1...
Rendering C2...
Rendering B1...
结论:
对于B1的兄弟组件C2来说B1的State变化对它没有影响。
步骤:
const C1m = React.memo(C1);
结果:
Rendering App...
Rendering B1...
Rendering C1...
Rendering C2...
Rendering B1...
结论:
React.memo()的包装保护了子组件,使其避免无关渲染
步骤:
const C1 = ({p1}) => {
console.log('Rendering C1...');
return (
<div className="C1">
<div>This is C1! {p1}</div>
</div>
);
};
<C1 p1={count} />
结果:
Rendering App...
Rendering B1...
Rendering C1...
Rendering B1...
Rendering C1...
结论:
C1的属性变化导致其重新渲染。祖父组件不会受影响。其父组件B1因为需要渲染C1的属性变化必然也要渲染。
步骤:
<C1m p1={count} />
结果:
Rendering App...
Rendering B1...
Rendering C1...
Rendering B1...
Rendering C1...
Rendering B1...
Rendering C1...
结论:
用memo包装后的C1的属性变化仍然触发了渲染。
步骤:
<C1m p1={999} />
结果:
Rendering App...
Rendering B1...
Rendering C1...
Rendering B1...
Rendering B1...
结论:
用memo包装后的C1的属性不变不会触发渲染。
步骤:
const [count, setCount] = useState(() => {
console.log('Init B1 state count.');
return 0;
});
结果:
Rendering App...
Init B1 state count.
Rendering B1...
Rendering B1...
Rendering B1...
结论:
useState的初始化函数确实只调用一次,state变化导致的重新渲染不会再次执行。
步骤:
const [count, setCount] = useState(() => {
console.log('Init C1 state count.');
return 0;
});
<div className="B1">
<button onClick={() => setCount(count + 1)} >{count}</button>
{(count%2 === 1) && <C1 />}
</div>
const [count, setCount] = useState(() => {
console.log('Init B1 state count.');
return 0;
});
结果:
Rendering App...
Init B1 state count.
Rendering B1...
Rendering B1...
Init C1 state count.
Rendering C1...
Rendering B1...
Init C1 state count.
Rendering C1...
结论:
useState的初始化函数会在挂载组件时调用,每次挂载都调用一次。
步骤:
useEffect(() => {
console.log('Calling B1 effect, count='+count);
});
结果:
Rendering App...
Rendering B1...
Calling B1 effect, count=0
Rendering B1...
Calling B1 effect, count=1
Rendering B1...
Calling B1 effect, count=2
结论:
不加触发条件的话每次渲染都会触发effect。
步骤:
useEffect(() => {
console.log('Calling B1 effect, count='+count);
}, []);
结果:
Rendering App...
Rendering B1...
Calling B1 effect, count=0
Rendering B1...
Rendering B1...
结论:
设触发条件为空数组的话初始化时会触发effect。
步骤:
useEffect(() => {
console.log('Calling B1 effect, count='+count);
}, []);
const App = () => {
const [p, setP] = useState(0);
console.log('Rendering App...');
setTimeout(() => {setP(3)}, 3000);
return (
<div id="app">
<B1 p2={p}/>
</div>
);
};
结果:
Rendering App...
Rendering B1...
Calling B1 effect, p2=0
Rendering B1...
Rendering B1...
Rendering App...
Rendering B1...
Calling B1 effect, p2=3
Rendering App...
结论:
设触发条件为属性的话不仅初始化时而且属性变化时也会触发effect。
又及:最后父组件App 又触发了一次渲染,有点意外。