React组件更新:探究两个典型案例

案例一

有时候我们可能会看到这样的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 源码时,重新思考这俩个问题,感觉豁然开朗!希望这些内容也能给你在学习中带来一些启发。

相关推荐
用户904438163246019 小时前
90%前端都踩过的JS内存黑洞:从《你不知道的JavaScript》解锁底层逻辑与避坑指南
前端·javascript·面试
PPPPickup20 小时前
easychat项目复盘---获取联系人列表,联系人详细,删除拉黑联系人
java·前端·javascript
老前端的功夫20 小时前
前端高可靠架构:医疗级Web应用的实时通信设计与实践
前端·javascript·vue.js·ubuntu·架构·前端框架
脾气有点小暴21 小时前
前端页面跳转的核心区别与实战指南
开发语言·前端·javascript
San30.1 天前
深入 JavaScript 内存机制:从栈与堆到闭包的底层原理
开发语言·javascript·udp
Fantastic_sj1 天前
Vue3相比Vue2的改进之处
前端·javascript·vue.js
Bigger1 天前
🚀 “踩坑日记”:shadcn + Vite 在 Monorepo 中配置报错
前端·react.js·vite
ttod_qzstudio1 天前
深入理解 TypeScript 数组的 find 与 filter 方法:精准查找的艺术
javascript·typescript·filter·find
冬男zdn1 天前
优雅处理数组的几个实用方法
前端·javascript
克喵的水银蛇1 天前
Flutter 通用标签选择组件:TagSelector 支持单选 / 多选
javascript·windows·flutter