TL;DR
本次演示 React 条件最佳实践,可以有效弥补 React 条件渲染没有类型安全和相近检查的缺点。
React 条件渲染的方式
根据官网和个人实践,React 条件渲染的方式一共有以下常见的四种方式:
- &&
- ?:
- if
- switch
现在给出四个组件如下:
const Apple = () => <span>苹果🍎🍏</span>;
const Kiwi = () => <span>猕猴桃🥝</span>;
const Cherry = () => <span>樱桃🍒</span>;
const Grape = () => <span>葡萄🍇</span>;
给出 Fruit 类型如下
type Fruit = 'apple' | 'kiwi' | 'cherry' | 'grape';
用 ConditionalFruitFacts 组件和 &&、?:、if、switch` 分别实现条件渲染:
&&
export const ConditionalFruitFacts = ({ fruit }: { fruit: Fruit }) => {
return (
<div>
{fruit === 'apple' && <Apple />}
{fruit === 'kiwi' && <Kiwi />}
{fruit === 'cherry' && <Cherry />}
{fruit === 'grape' && <Grape />}
</div>
);
};
?:
export const ConditionalFruitFacts = ({ fruit }: { fruit: Fruit }) => {
return (
<div>
{fruit === 'apple' ? <Apple /> : null}
{fruit === 'kiwi' ? <Kiwi /> : null}
{fruit === 'cherry' ? <Cherry /> : null}
{fruit === 'grape' ? <Grape /> : null}
</div>
);
};
if
export const ConditionalFruitFacts = ({ fruit }: { fruit: Fruit }) => {
let fruitElement: ReactNode;
if(fruit === 'apple' ){
fruitElement = <Apple />;
} else if(fruit === 'kiwi'){
fruitElement = <Kiwi />;
} else if(fruit === 'cherry'){
fruitElement = <Cherry />;
} else if(fruit === 'grape'){
fruitElement = <Grape />
}
return fruitElement;
};
从简洁性上 if 不如 Switch,而它俩都不如 && 和 ?:,其中 && 做条件判断又没有 ?: 安全。
switch
export const ConditionalFruitFacts = ({ fruit }: { fruit: Fruit }) => {
return (
<div>
{(() => {
switch (fruit) {
case 'apple':
return <Apple />;
case 'kiwi':
return <Kiwi />;
case 'cherry':
return <Cherry />;
case 'grape':
return <Grape />;
default:
return null;
}
})()}
</div>
);
};
四种条件渲染的缺点
缺点如下:
- 宽松的类型安全:现在我给 Fruit 类型添加一个 banana 字面量类型,就改动这一处,代码无任何类型报错,但此时理想的效果应该是,条件判应该通过类型提示我们添加 Banana 组件。
type Fruit = 'apple' | 'kiwi' | 'cherry' | 'grape' | 'banana';
- 没有详尽检查:接第一条的操作,现在我这样调用组件
<ConditionalFruitFacts fruit="banana"/>,组件是不会报错的,代码上这没有问题,但理想的效果应该是让组件报错,因为 Banana 组件没有添加,虽然不报错,但调用是不安全的,我们希望此处调用有模式匹配中详尽检查的效果。
什么是详尽检查?
模式匹配(pattern matching)类似 JS 中的条件判断,不同的是模式匹配(pattern matching)有详尽检查的特点。
在 JS 中条件判断即使超出枚举条件也不会报错,比如:
function exhaustiveSwitch(value) {
switch (value) {
case 1:
return 'Case 1';
case 2:
return 'Case 2';
case 3:
return 'Case 3';
}
}
console.log(exhaustiveSwitch(88888));
而在模式匹配中则不是,比如 Rust 中:
fn main() {
let x = 9;
match x {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
}
}
因为没有默认匹配条件 _ => println!("Something else"),Rust 编译报错。
最佳实践
最佳实践就是利用 Typescript 的类型检查:
export const ConditionalFruitFacts = ({ fruit }: { fruit: Fruit }) => {
const icon: Record<Fruit, ReactNode> = {
apple: <Apple />,
kiwi: <Kiwi />,
cherry: <Cherry />,
grape: <Grape />,
};
return icon[fruit];
};
给 Fruit 类型添加一个 banana 字面量类型,利用 TypeScript 的类型检查,变量 icon 直接类型提示缺少 banana 属性(解决了提出的问题 1),当我们添加上 banana 这个 key 顺便健全了条件(解决了提出的问题 2)。
完整代码如下: