一个bug引起关于react解析渲染的思考

2024-05-07
12345 分钟
...

先看如下代码

bug的代码很简单 实现的逻辑就是父组件通过一个开关 判断是否可以渲染子组件的内容。默认renderChildren=false 即不渲染子组件...

import React from 'react';
function ParentComponent({ children, shouldRenderChildren }) {
  return (
    <div>
      {shouldRenderChildren ? children : null}
    </div>
  );
}

function ChildComponent() {
  return <div>子组件被渲染了</div>;
}

export default function App() {
  const renderChildren = false;  
  return (
    <ParentComponent shouldRenderChildren={renderChildren}>
      <ChildComponent />
      <div>{a.b.c}</div>
    </ParentComponent>
  );
}

先有问题再有答案

  1. 上面的代码能正常运行嘛
  2. 会报什么错误
  3. 为什么会报错
  4. 如何改正使其不报错

预期结果

renderChildren=false时 通过父组件的条件语句控制,child组件没有被执行到 所以不会报错。
此时组件结构如图所示:

renderChildren=true时 child组件被执行,当执行a.b.c时因为a未定义 所以child组件发生报错。
此时组件结构如图所示:

实际结果

无论renderChildren=true还是renderChildren=false 都会发生a.b.c的执行错误,导致渲染异常。

分析

第一个问题:无论renderChildren是true还是false 都会发生渲染异常,这是为什么?

第二个问题:错误是发生在app上的 并非我们之前预期的child上 这又是为什么?

这是因为 JSX 中花括号 {}的解析执行的行为依赖于它们包含的表达式的位置和上下文。

具体的执行步骤如下:

  1. 当 React 开始处理 App 组件时,它会解析其中的 JSX。此时,它需要执行 JSX 中的所有 JavaScript 表达式以确定它们的值,这包括传递给 ParentComponent 的 children 属性中的所有内容。(因为他们是写在app的jsx中的, 是app组件的一部分)
  2. 在尝试解析执行 {a.b.c} 时,由于 a 未定义,访问 a.b 时就会触发运行时错误. 此错误发生在任何子组件被渲染之前,同时也发生在基于 shouldRenderChildren 值是否要渲染 children 之前。
  3. 由于在解析 JSX 期间发生了 JavaScript 错误,整个组件树的渲染过程会被中断。React 不会继续执行任何渲染逻辑或进一步处理组件树,因此后续所有组件,实际上不会被渲染到 DOM 中。
  4. 所以无论 shouldRenderChildren 的值如何,由于在更早之前就已经发生解析执行错误,整个应用的渲染流程已经停止。

修改

第一种方式

{a.b.c}移动到子组件内部 改变表达式的作用域 这样当shouldRenderChildren=false时 并不会执行子组件的代码,可以实现预期效果。

import React from 'react';
function ParentComponent({ children, shouldRenderChildren }) {
  return (
    <div>
      {shouldRenderChildren ? children : null}
    </div>
  );
}

function ChildComponent() {
  return <div>子组件被渲染了<div>{a.b.c}</div></div>;
}

export default function App() {
  const renderChildren = false;  
  return (
    <ParentComponent shouldRenderChildren={renderChildren}>
      <ChildComponent />
    </ParentComponent>
  );
}

第二种方式

通过将内容包装为一个函数的方式 延迟组件为true时函数在执行。

import React from "react";
function ParentComponent({ renderContent, shouldRenderChildren }) {
  return <div>{shouldRenderChildren ? renderContent() : null}</div>;
}
function ChildComponent() {
  return <div>子组件被渲染了</div>;
}
export default function App() {
  const renderChildren = false; 
  return (
    <div>
      <ParentComponent
        shouldRenderChildren={renderChildren}
        renderContent={() => {
          return (
            <>
              <ChildComponent />
              <div>{a.b.c}</div>
            </>
          );
        }}
      ></ParentComponent>
    </div>
  );
}

总结&思考

透过现象看本质 仔细想想 之前的预期结果是不合理的 是经不起推敲的。我们错误的认为写在children属性下的表达式和组件是一样的 会在子组件内部被执行。实际上如果我们从函数的角度来思考这个问题就不会导致错误的预期结果。

jsx实际一种语法糖,经过编译后我们写的组件会被转换为函数,所谓的组件树实际上是一个嵌套的函数树,被包裹在 {} 中的表达式会在其所属的 JSX 表达式被转换成函数调用前就被计算出来。这意味着表达式的结果 无论是字符串、数字、组件还是任何其他类型的值都会在传递给组件(即函数)之前确定下来。想要在子函数中动态获取值 唯一的方式是将其转换为一个函数。

以上是我的一个理解 如有问题 欢迎大佬们和我讨论互相交流学习....

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
react2025-12-02
useEffect与useLayoutEffect对比
在 React Hooks 中, 和 都用于处理副作用逻辑,例如订阅、事件监听、DOM 操作等。但它们有着本质性的执行时机差异,这影响到页面渲染、布局抖动、性能表现等关键点。 1\. 执行时机的核心区别 在 React 的渲染流程中: 1. 渲染(Render phase) React 根据 state/props 计算 UI,生成虚拟 DOM,不访问真实 DOM。 2. 提交(Commit phase) React 将虚拟 DOM 变...
学习
react2025-08-03
在 React 中实现倒计时功能会有什么坑
倒计时 倒计时是一个非常常见的业务场景,但是在 React 中实现起来,却不算简单。 首先我们来看这段倒计时代码,它能否正常执行? 来看看实际表现效果 可以看到计时器在不断执行,但是 的值却没有变。 这是一个很经典的问题:React 闭包陷阱 。 我们来详细分析下: 的依赖数组 是空的,这意味着 effect 只会在组件挂载时执行一次,而不会在 状态更新时重新执行。所以 回调函数中捕获的 值始终是初始值 。 这个问题解决起来也很简单,有...
学习面试
react2025-07-30
React性能优化三剑客:memo、useMemo和useCallback详解
前言 在React开发中,性能优化是一个永恒的话题。今天我们就来深入探讨React提供的三个重要性能优化工具: 、 和 ,它们如何帮助我们构建更高效的React应用。 1\. 为什么需要性能优化? 🤔 React的核心机制是当组件的state或props发生变化时,组件会重新渲染。但有时这种重新渲染是不必要的: 父组件更新导致所有子组件重新渲染 ,即使子组件的props没有变化 复杂计算在每次渲染时重复执行 ,消耗大量资源 函数引用在...
学习面试
react2025-06-05
React 中 useDeferredValue 和 startTransition 的核心区别与使用场景
React 中 和 ,两者都用于优化性能,但适用场景和实现方式不同。 核心概念解析 1\. useDeferredValue:延迟值更新 作用 :告诉 React 延迟更新某个值 ,直到所有高优先级渲染任务完成。 适用场景 :当某个值的更新会触发计算密集型渲染(如复杂列表、图表),但需要优先保证其他高优先级 UI(如输入框)的响应。 机制 : 保持旧值显示,后台计算新值。 计算完成后,用新值更新 UI,期间允许用户继续交互。 示例 :...
学习
react2024-12-18
Antd 样式覆盖
&nbsp; 目前作者所在的业务正在升级 Antd5.0,不得不说 Antd5.0 真的太香了,但是由于团队的 UE 规范进行了大改版,Antd5.0 所有的 必须对齐最新的规范,因此就需要对 Antd5.0 组件做主题定制。 现状 目前针对项目中使用的 antd 高频组件进行了梳理,并且根据规范基于 design token 对这些高频组件进行样式定制,对于主题色、圆角、边框、字体等用户 高的主题都已经满足团队规范。 虽然 Desig...
学习
react2024-12-06
React 19 终于发布,一大波新功能正式升级
这篇文章主要介绍了 React 19 的新功能,包括 Actions 自动处理数据突变相关状态,新增的 useActionState、useFormStatus、useOptimistic 等 hook,新的 use API 读取渲染资源,新的 React DOM 静态 API 生成静态站点,以及 React 服务器组件中的 RSC 和 RSA 等内容。同时提到后续会分享针对旧版功能的优化和改良。 关联问题: React 19 性能如何...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录