透过现象看本质:一个bug引起关于react解析渲染的思考
背景
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>
);
}
先有问题再有答案
上面的代码能正常运行嘛
会报什么错误
为什么会报错
如何改正使其不报错
预期结果
当renderChildren=false
时 通过父组件的条件语句控制,child组件没有被执行到
所以不会报错。
此时组件结构如图所示:
当renderChildren=true
时 child组件被执行,当执行a.b.c
时因为a未定义 所以child组件发生报错。
此时组件结构如图所示:
实际结果
无论renderChildren=true
还是renderChildren=false
都会发生a.b.c
的执行错误,导致渲染异常。
分析
第一个问题:无论renderChildren是true还是false 都会发生渲染异常,这是为什么?
第二个问题:错误是发生在app上的 并非我们之前预期的child上 这又是为什么?
这是因为 JSX 中花括号 {}的解析执行的行为依赖于它们包含的表达式的位置和上下文。
具体的执行步骤如下:
- 当 React 开始处理 App 组件时,它会解析其中的 JSX。此时,它需要执行 JSX 中的所有 JavaScript 表达式以确定它们的值,这包括传递给 ParentComponent 的 children 属性中的所有内容。(
因为他们是写在app的jsx中的, 是app组件的一部分
) - 在尝试解析执行 {a.b.c} 时,由于 a 未定义,访问 a.b 时就会触发运行时错误. 此错误发生在任何子组件被渲染之前,同时也发生在基于 shouldRenderChildren 值是否要渲染 children 之前。
- 由于在解析 JSX 期间发生了 JavaScript 错误,整个组件树的渲染过程会被中断。React 不会继续执行任何渲染逻辑或进一步处理组件树,因此后续所有组件,实际上不会被渲染到 DOM 中。
- 所以无论 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 表达式被转换成函数调用前就被计算出来。这意味着表达式的结果 无论是字符串、数字、组件还是任何其他类型的值都会在传递给组件(即函数)之前确定下来。想要在子函数中动态获取值 唯一的方式是将其转换为一个函数。
以上是我的一个理解 如有问题 欢迎大佬们和我讨论互相交流学习....
转载自:https://juejin.cn/post/7365831823928557618