案例一
有时候我们可能会看到这样的react 代码:
javascript
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 的方式来定义组件。
案例二
下面我们再看一段代码:
tsx
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编译以后的代码:

为了方便理解,我们将代码进行简化:
tsx
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 源码时,重新思考这俩个问题,感觉豁然开朗!希望这些内容也能给你在学习中带来一些启发。