## 1 引言

本期精读的文章是：[8 React conditional rendering methods](https://blog.logrocket.com/conditional-rendering-in-react-c6b0e5af381e)

介绍了八种 React 条件渲染方式。

模版条件渲染非常常见，遇到的时候往往会随机选择一种方式使用，那么怎么写会有较好的维护性呢？先一起了解下有哪八种条件渲染方式吧！

## 2 概述

### IF/ELSE

既然 JSX 支持 js 与 html 混写，那么交替使用就能解决条件渲染的问题：

```tsx
function render() {
  if (renderComponent1) {
    return <Component1 />;
  } else {
    return <div />;
  }
}
```

### return `null`

如果不想渲染空元素，最好使用 `null` 代替空的 `div`：

```tsx
function render() {
  if (renderComponent1) {
    return <Component1 />;
  } else {
    return null;
  }
}
```

这样对 React 渲染效率有提升。

### 组件变量

将组件赋值到变量，就可以在 return 前任意修改它了。

```tsx
function render() {
  let component = null;

  if (renderComponent1) {
    component = <Component1 />;
  }

  return component;
}
```

### 三元运算符

[三元运算符](https://en.wikipedia.org/wiki/%3F:)的语法如下：

```text
condition ? expr_if_true : expr_if_false
```

用在 JSX 上也很方便：

```tsx
function render() {
  return renderComponent1 ? <Component1 /> : null;
}
```

但三元运算符产生嵌套时，理解成本会变得很高。

### &&

这个是最常用了，因为代码量最少。

```tsx
function render() {
  return renderComponent1 && <Component1 />;
}
```

### IIFE

IIFE 含义是立即执行函数，也就是如下代码：

```typescript
(function myFunction(/* arguments */) {
  // ...
})(/* arguments */);
```

当深陷 JSX 代码中，又想写一大块逻辑时，除了回到上方，还可以使用 IIFE：

```tsx
function render() {
  return (
    <div>
      {(() => {
        if (renderComponent1) {
          return <Component1 />;
        } else {
          return <div />;
        }
      })()}
    </div>
  );
}
```

### 子组件

这是 IIFE 的变种，也就是把这段立即执行函数替换成一个普通函数：

```tsx
function render() {
  return (
    <div>
      <SubRender />
    </div>
  );
}

function SubRender() {
  if (renderComponent1) {
    return <Component1 />;
  } else {
    return <div />;
  }
}
```

### IF 组件

做一个条件渲染组件 `IF` 代替 js 函数的 `if`：

```tsx
<If condition={true}>
  <span>Hi!</span>
</If>
```

这个组件实现也很简单

```tsx
const If = props => {
  const condition = props.condition || false;
  const positive = props.then || null;
  const negative = props.else || null;

  return condition ? positive : negative;
};
```

### 高阶组件

高阶组件，就是返回一个新组件的函数，并且接收一个组件作为参数。

那么我们就能在高阶组件里写条件语句，返回不同的组件即可：

```tsx
function higherOrderComponent(Component) {
  return function EnhancedComponent(props) {
    if (condition) {
      return <AnotherComponent {...props} />;
    }

    return <Component {...props} />;
  };
}
```

## 3 精读

这么多方法都能实现条件渲染，那么重点在于可读性与可维护性。

比如通过调用函数实现组件渲染：

```tsx
<div>{renderButton()}</div>
```

看上去还是比较冗余，如果使用 `renderButton` getter 定义，我们就可以这么写它：

```tsx
<div>{button}</div>
```

其实我们想要的就是 button，而不是 `renderButton`。那么还可以进一步，干脆封装成 JSX 组件：

```tsx
<div>
  <Button />
</div>
```

是否要付出这些努力，取决于应用的复杂度。如果应用复杂度非常高，那你应当尽量使用最后一种封装，让每个文件的逻辑尽量独立、简单。

如果应用复杂度比较低，那么注意不要过度封装，以免把自己绕进去。

所以看来这又是一个没有固定答案的问题，选择何种方式封装，取决于应用复杂度。

### 应用复杂度

对任何代码封装，都会增加这段 **连接逻辑** 的复杂度。

假定无论如何代码的复杂度都是恒定不变的，下面这段代码，连接复杂度为 0，而对于 `render` 函数而言，逻辑复杂度是 100：

```tsx
function render() {
  if (renderComponent) {
    return isOk ? <Component1 /> : <Component2 />;
  } else {
    return <div />;
  }
}
```

下面这段代码拆成了两个函数，逻辑复杂度对 `render` `SubComponent` 来说都是 50，但连接复杂度是 50：

```ts
function render() {
  if (renderComponent) {
    return <SubComponent>;
  } else {
    return <div />;
  }
}

function SubComponent() {
  return isOk ? <Component1 /> : <Component2 />
}
```

可以看到，我们通过函数拆分，降低了每个函数的逻辑复杂度，但却提高了连接复杂度。

下面来做一个比较，我们假设一个正常的程序员，可以一次性轻松记忆 10 个函数。如果再多，函数之间的调用关系就会让人摸不着头脑。

#### 应用较小时

在应用代码量比较小时，假设一共有 10 个函数，如果做了逻辑抽象，拆分出了 10 个子函数，那么总逻辑复杂度不变，函数变成了 20 个。

此时小王要修改此项目，他需要找到关键代码的位置。

如果没有做逻辑抽象，小王一下子就记住了 10 个函数，并且很快完成了需求。

如果应用做了逻辑抽象，他需要理解的逻辑复杂度是不变的，但是要读的函数变成了 20 个。小王需要像侦探一样在线索中不断跳转，他还是只找了 10 个关键函数，但一共也就 20 个函数，逻辑并不复杂，这值得吗？

小王心里可能会嘀咕：简单的逻辑瞎抽象，害我文件找了半天！

#### 应用较大时

此时应用代码量比较大，假设一共有 500 个函数，我们不考虑抽象后带来的复用好处，假设都无法复用，那么做了逻辑抽象后，那么总逻辑复杂度不变，函数变成了 1000 个。

此时小王接到了需求，终于维护了一个大项目。

小王知道这个项目很复杂，从一开始就没觉得能理解项目全貌，所以把自己当作一名侦探，准备一步步探索。

现在有两种选择，一种是在未做逻辑抽象时探索，一种是在做过逻辑抽象后探索。

如果没做逻辑抽象，小王需要面对 `500` 个这种函数：

```tsx
function render() {
  if (renderComponent) {
    return isOk ? <Component1 /> : <Component2 />;
  } else {
    return isReady ? <Component3 /> : <Component4 />;
  }
}
```

如果做了逻辑抽象，小王需要面对 `1000` 个这种函数：

```tsx
function render() {
  if (renderComponent) {
    return <Component1And2 />;
  } else {
    return <Component3And4 />;
  }
}
```

在项目庞大后，总函数数量并不会影响对线索的查找，而总线索深度也几乎总是固定的，一般在 5 层左右。

小王理解 5 个或 10 个函数成本都差不多，但没有做逻辑抽象时，这 5 个函数各自参杂了其他逻辑，反而影响对函数的理解。

这时做逻辑抽象是合适的。

## 4 总结

所以总的来说，笔者更倾向使用子函数、子组件、IF 组件、高阶组件做条件渲染，因为这四种方式都能提高程序的抽象能力。

往往抽象后的代码会更具有复用性，单个函数逻辑更清晰，在切面编程时更利于理解。

当项目很简单时，整个项目的理解成本都很低，抽象带来的复杂度反而让项目变成了需要切面编程的时候，就得不偿失了。

总结一下：

- 当项目很简单，或者条件渲染的逻辑确认无法复用时，推荐在代码中用 `&&` 或者三元运算符、IIFE 等直接实现条件渲染。
- 当项目很复杂时，尽量都使用 子函数、子组件、IF 组件、高阶组件 等方式做更有抽象度的条件渲染。
- 在做逻辑抽象时，考虑下项目的复杂度，避免因为抽象带来的成本增加，让本可以整体理解的项目变得支离破碎。

## 5 更多讨论

> 讨论地址是：[精读《React 八种条件渲染》 · Issue #90 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/90)

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。**
