更简洁的 React:条件渲染
由于条件渲染的存在,React 组件常常变得难以理解。起初,一个简单的 if/else 语句或三元运算符似乎对整体可读性影响不大,但随着时间的推移和代码的变更,可能会添加更多的 if/else 语句或三元运算符。
更糟糕的是,条件运算符会被多次嵌套,而这种情况又很容易发生。
我们先来看看如何在 React 中进行条件渲染,然后深入研究几个实验,这些实验可能会展示在 React 中进行条件渲染的更易读的方法。
条件渲染样式
简单场景
场景: 根据属性“isLoggedIn”显示登录组件还是注册组件
使用 &&
“&&”符号经常被使用,因为它很容易插入,可以快速实现一些条件逻辑。
const Session = ({ isLoggedIn }) => {
return (
<>
{isLoggedIn && <Login />}
{!isLoggedIn && <SignOut />}
</>
);
};
使用 If/Else 语句
在这种简单的场景下,使用 guard 子句是可行的,而且比“&&”更易读。
const Session = ({ isLoggedIn }) => {
if (isLoggedIn) {
return <SignOut />
}
return <Login />
};
使用三元运算符
这样也更容易理解;能够用一行文字表达出来真是太好了。
const Session = ({ isLoggedIn }) => isLoggedIn ? <SignOut /> : <Login />;
复杂情景
场景:根据属性“isLoggedIn”显示登录组件还是注册组件,此外,如果“isUnicorn”标志为真,则显示“UnicornLogin”组件。
使用 &&
这太糟糕了。很明显,“&&”符号只能谨慎使用,而且只能在只有一个条件时使用。
const Session = ({ isLoggedIn, isUnicorn }) => {
<>
{isLoggedIn && !isUnicorn && <Login />}
{!isLoggedIn && isUnicorn && <isUnicorn />}
{!isLoggedIn && !isUnicorn && <SignOut />}
</>;
};
使用 If/Else 语句
虽然没那么糟糕,但如果你想用另一个组件包裹每个返回的组件,这将使事情变得棘手。
const Session = ({ isLoggedIn, isUnicorn }) => {
if (isLoggedIn) {
return <SignOut />;
} else if (isUnicorn) {
return <UnicornLogin />;
};
return <Login />;
};
使用三元运算符
此外,虽然没那么糟糕,但当不可避免地发生更多变化时,它仍然会遇到与使用 if/else 语句相同的问题。
const Session = ({ isLoggedIn, isUnicorn }) => {
if (isLoggedIn) {
return <SignOut />;
}
return isUnicorn ? <UnicornLogin /> : <Login />;
};
条件陷阱
现在我们已经了解了如何在 React 中使用条件渲染,接下来让我们来看一些具体的例子,在这些例子中,条件渲染可能不会像你预期的那样工作。
逻辑运算符“&&”
映射时渲染 0
使用“&&”检查数据集的长度,然后对其进行映射时,如果该列表为空,则会渲染“0”。
Kent C. Dodds 最近在他的文章https://kentcdodds.com/blog/use-ternaries-rather-than-and-and-in-jsx中重点强调了这一点。
以下代码会在数据为空时显示“0”。
const RenderData = ({ data }) => data.length && data.map(...);
这可以通过使用三元运算符代替“&&”来解决。
const RenderData = ({ data }) => data.length > 0 ? data.map(...) : null;
这也可以通过使用 if/else 语句来解决。
const RenderData = ({ data }) => data.length > 0 ? data.map(.const RenderData = ({ data }) => {
if (data.length === 0) return null;
return data.map(...)
}
在组件中渲染数字 0
这是 React Native 特有的问题,当有条件地渲染组件并传入条件时,可能会无意中渲染出 0。这会导致应用程序崩溃,并出现以下错误信息:“Invariant Violation: Text strings must be render within a component.”
如果“message”的值等于0,这将导致您的应用程序崩溃:
message && <Text>{message}</Text>
嵌套三元组
如果只有一个条件,三元运算符确实很方便。但是,它很容易导致代码不进行重构,很快又会添加另一个检查,然后又添加另一个。
这是一个简单的例子,你可以想象一下,如果我们渲染的每个组件都有 5-10 行或更多行,那会是什么样子。
const RenderData = ({ data }) => {
return data.length === 0 ? null
: data.length === 1
? <SingleItem data={data} />
: data.length === 2
? <DoubleItem data={data} />
: <MultiItem data={data} />
}
写作条件更佳
我们已经了解了如何在 React 中编写基本的条件语句,以及一些需要避免的陷阱。接下来,让我们探讨如何在 React 中编写更好的条件代码。
条件运算符组件
我认为,如果大脑只需要解析 JSX 而不是条件语句,那么阅读起来会更容易。那么,我们如何用 XML 编写条件运算符呢?
让我们考虑创建一个名为“RenderIf”的组件,它接受一个布尔属性“isTrue”,并渲染其子组件。
export const RenderIf = ({ children, isTrue }) => isTrue ? children : null;
RenderIf.propTypes = {
children: node.isRequired,
isTrue: bool.isRequired,
};
我用“RenderIf”组件重写了我们的示例,主要关注的是XML。不过,其中仍然有一些布尔逻辑可以进一步优化。
const RenderData = ({ data }) => {
return (
<>
<RenderIf isTrue={data.length === 1}>
<SingleItem data={data} />
</RenderIf>
<RenderIf isTrue={data.length === 2}>
<DoubleItem data={data} />
</RenderIf>
<RenderIf isTrue={data.length > 2}>
<MultiItem data={data} />
</RenderIf>
</>
);
}
我们可以通过包装“RenderIf”组件来简化布尔逻辑。
const IfSingleItem = ({ children, data }) => <RenderIf isTrue={data.length === 1}>{children}</RenderIf>
const IfDoubleItem = ({ children, data }) => <RenderIf isTrue={data.length === 2}>{children}</RenderIf>
const IfMultiItem = ({ children, data }) => <RenderIf isTrue={data.length > 3}>{children}</RenderIf>
const RenderData = ({ data }) => {
return (
<>
<IfSingleItem data={data}>
<SingleItem data={data} />
</IfSingleItem>
<IfDoubleItem data={data}>
<DoubleItem data={data} />
</IfDoubleItem>
<IfMultiItem data={data}>
<MultiItem data={data} />
</IfMultiItem>
</>
);
}
鱼与熊掌兼得!
我个人更喜欢声明式 React 代码,但我之前没提到的一个问题是,即使条件为假,RenderIf 组件的子组件仍然会经历一个渲染周期。这是因为 RenderIf 仍然是 JSX,而不是纯 JavaScript。
那么,我们该如何解决这个问题呢?
我恰好写了一个RenderIfBabel 插件,它就能做到这一点!你可以在我的 GitHub 上找到代码,链接在这里。
本质上,这个插件会接收类似这样的代码:
<RenderIf isTrue={someCondition}>
<span>I am the children</span>
</RenderIf>
并将其改为这样:
{someCondition ? <span>I am the children</span> : null
所以,我们实现了声明式语法,并且在转译后得到了性能更高的版本。此外,如果您使用此插件,您就无需编写自己的 RenderIf 组件了!🎉
何时重构
通常情况下,如果某个组件的复杂性不断累积,就表明其中某些组件需要重构。虽然很难准确判断何时以及如何重构,但以下是一些您可以参考的通用经验法则。
100多行代码
尽量将组件代码控制在 100 行以内。当代码量达到 100-250 行时,就应该认真考虑重构了。如果代码超过 500 行,则必须尽快重构。
高环路复杂度
圈复杂度是指代码中路径的数量。因此,如果只有一个简单的 if/else 代码块,那么它的圈复杂度为 2;而如果有一个 if/else if/else if/else if/else 的代码块,那么它的圈复杂度就是 5。
合适的复杂程度取决于您自己,但通常 4-5 级左右是一个不错的起点。
更简洁的 React
我们可以通过去除分散注意力的语法并了解何时重构来编写更简洁的 React 代码。
创建像“RenderIf”这样的辅助组件,就是一个将条件逻辑提取到声明式语法中的一个例子。由于它主要处理的是 XML,因此可以减轻你的理解负担。基于这个思路,我们可以封装辅助组件,从而创建更丰富的条件组件集,并添加更多上下文信息。
归根结底,一个庞大而复杂的组件,无论 React 代码多么简洁,都容易出现 bug,而且开发起来也令人头疼。因此,了解何时需要重构,并在意识到需要重构时果断执行,是一种良好的实践。
祝您编码愉快,并保持 React 代码的整洁!
文章来源:https://dev.to/sturdynut/cleaner-react-conditional-rendering-h34