React组件更新:探究两个典型案例
React组件更新:探究两个典型案例
案例一
有时候我们可能会看到这样的react
代码:
const Child1 = () => {
return <div>Child1</div>;
};
function App() {
const Child2 = () => {
return <div>Child2</div>;
};
return (
<div>
<Child1 />
<Child2 />
</div>
);
}
在过去,我傻傻地以为像 Child2
这样把组件定义在另一个组件内部和 Child1
没啥区别。但事实证明,这是大错特错!原来我们应该一直像 Child1
那样定义组件,因为 Child2
会招来巨大的性能麻烦。
当 App
组件更新时,会重新执行一次 App
函数。由于 App
函数内部每次都要新建一个 Child2
,经过 Babel 编译后,它就成了 {type: Child2,…}
。React 在组件更新时,会比较新旧节点的 type
是否相等,来决定是否复用老元素。然而,由于 type → Child2
永远是新函数,就算长得和老的一模一样,React 也会狠心地删除旧的 Child2
,然后又创建一个一模一样的新 Child2
。
所以我们应该一直使用Child1
的方式来定义组件。
案例二
下面我们再看一段代码:
function App() {
return (
<Child>
<GrandChild />
</Child>
);
}
const GrandChild = () => {
console.log('render-grand-child');
return <div>GrandChild</div>;
};
const GrandChild1 = () => {
console.log('render-grand-child1');
return <div>GrandChild1</div>;
};
const Child = (props: any) => {
const [count, setCount] = useState(0);
const onClick = () => {
setCount(count + 1);
};
return (
<div onClick={onClick}>
<h2>{count}</h2>
{/* 通过children属性传入 */}
{props.children}
{/* 直接写到Child组件中 */}
<GrandChild1 />
</div>
);
};
当 Child
组件更新时,GrandChild1
会重新执行,而 GrandChild
就好像没事发生一样。
我们看一下在babel编译以后的代码:

为了方便理解,我们将代码进行简化:
const jsx = (type,props) => {
return {
type,
props
}
}
const GrandChildElement = jsx(GrandChild,{})
// 省略其它无关元素
const Child = () => {
return (
{GrandChild1Element}
jsx(GrandChild1,{})
)
}
可以看到,每次 Child
重新执行时,都要重新创建一个新的 props
用于 GrandChild1
,而 GrandChild
则复用了老的已创建好的 props
。React 在组件更新时,会比对新旧节点的 props
是否相同,以决定是否重新执行函数组件。所以 GrandChild1
被重新执行,而 GrandChild
由于与老节点使用相同的 props
,因此不会重新执行。
这俩个问题曾经困扰了我很久,最近在阅读React源码时,又再次重新理解了这些代码的执行细节。希望这些内容也能对你理解React代码有帮助
这俩问题曾困扰了我很久,直到最近我在研究 React 源码时,重新思考这俩个问题,感觉豁然开朗!希望这些内容也能给你在学习中带来一些启发。