本期精读的文章是：[我不再使用高阶组件](https://medium.com/tandemly/im-breaking-up-with-higher-order-components-44b0df2db052)。

懒得看文章？没关系，稍后会附上文章内容概述，同时，更希望能通过阅读这一期的精读，穿插着深入阅读原文。

## 1 引言

<img src="https://img.alicdn.com/imgextra/i2/O1CN015wM4ZL1pMW2ZRbwmj_!!6000000005346-2-tps-600-551.png" alt="logo" width="500" />

> React 与 Vue 相比，组件为一等公民是最大特色之一。

由于组件可以作为一个 props 向下传递，因此 React 具备了高度抽象化的能力，Vue 虽然更易上手，但因 template 特点，没有所谓 props 传递组件这种概念，但这样导致在抽象能力上落后于 React。可能这是 JSX 与 template 之间的差异吧，也是变量与字符串之间的差异，变量同名但含义不同，所以可抽象，而模版靠规则和名称确定含义。

当然 Vue 也有 [babel-plugin-transform-vue-jsx](https://www.npmjs.com/package/babel-plugin-transform-vue-jsx) 这里不做展开。

强大的组件能力，导致了实践的多样性，高阶组件就是其一。高阶组件的特点是，JSX 描述的子元素，会注入到父级组件的 `this.props.children` 中，因此可以无入侵增强组件能力，常用比如权限、跳转、埋点、异常、描述、注入等等。

高阶组件也带来了使用中的困扰，作者这篇文章阐述了高阶组件存在的问题，值得我们了解。

## 2 内容概要

高阶组件由于可嵌套，如果有一环高阶组件没有将内部 `wrappedComponent` 暴露出来，会导致后续叠加的高阶组件都无法获取、注入到原始组件。

另外就算所有高阶组件都遵循了规范，组件也难以察觉被注入的数据是由哪些高阶组件提供的，而且高阶组件之间互相隔离，导致可能存在覆盖 props 的危险情况，这些问题高阶组件都束手无策。这体现出约定比约束更加效率，但约定的可维护性低于约束。

因此更好的解决思路可能是叫做 `render props` `render callback` `function as child` 这些名字的方法，组件定义如下：

```javascript
// Contrived example for simplicity
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Caffeinate extends Component {
  propTypes = { children: PropTypes.func.isRequired };
  state = { coffee: "Americano" };
  render() {
    return this.props.children(this.state.coffee);
  }
}
```

直接将函数作为子元素，可以认为是一个匿名组件：

```javascript
render(
  <Caffeinate>
    {(beverage) => <div>Drinking an {beverage}.</div>}
  </Caffeinate>,
  document.querySelector("#root")
);
//=> Drinking an Americano.
```

这种用法在 [React Motion](https://github.com/chenglou/react-motion) [React Router](https://reacttraining.com/react-router/) 里都有采用。

## 3 精读

### 本质是将组件作为参数

我们看另一种写法：

```javascript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Caffeinate extends Component {
  propTypes = { children: PropTypes.func.isRequired };
  state = { coffee: "Americano" };
  render() {
    return this.props.child(this.state.coffee);
  }
}

// usage
render(
  <Caffeinate child={
    (beverage) => <div>Drinking an {beverage}.</div>
  } />,
  document.querySelector("#root")
);
```

`render props` 本质上与上面这种很常规的写法没什么不同，差异在于利用了 `props.children`，将参数写在 JSX 的子元素中。相比高阶组件用法，这样嵌套下来，看得清楚数据流动，解决了高阶组件反复嵌套导致的各类问题：

```javascript
render(
  <RenderProps1>
    {(title) => <div>
      <h1>{title}</h1>
      <RenderProps2>
        {(name) =>
          <RenderProps3>
            {(age) => {
              <div>{name}, {age}</div>
            }}
          </RenderProps3>
        }
      </RenderProps2>
    </div>}
  </RenderProps1>
)
```

### 与高阶组件对比

与 HOC 相比，render props 开放性提升明显，原本 HOC 所做的功能抽象可通过 render Props 获取，而 render 也可以访问到父级的一切：

![compare with high order component](https://user-images.githubusercontent.com/9314735/32509788-5812c6f2-c429-11e7-8ff5-b088cacf9ca9.png)

### Render Props 存在的问题

1. `this.props.children` 不该作为函数调用。
2. 渲染粒度变大，表格等需要性能优化的场景不适合。
3. renderProps 渲染的并不是 React 组件，无法为其单独使用 `redux`，`mobx` `dob` 等依赖收集粒度也放不下去。

`renderProps` 为了解耦，让控制权从上到下传递，而底层实现不需要了解上层实现，这是解决 JSX 修改组件模版问题的方法之一，作为优化点之一，可以考虑让传入的 props 自身作为一个组件：

```javascript
const View = ({title}) => <div>{title}</div>

// ...
render() {
  return (
    <Component view={View} />
  )
}
```

### 简约即美

与其绕那么大一圈，还不如回归到最普通的 props 传参，这说明 `renderProps` 作为其中一种特例，可能观赏价值大于其实用价值。其控制放权的思想也是为了解决组件 dom 结构定制化的问题。

但是这也涉及到限度的问题，以下就是两种极端：

```javascript
render() {
  return this.props.children
}
```

```javascript
render() {
  return (
    <div>
      <Header />
      <Sidebar>
        <Toolbox>
          <ul>
            <li>..</li>
            <li>{this.props.secondLi}</li>
            <li>..</li>
          </ul>
        </Toolbox>
      </Sidebar>
      <Footer />
    </div>
  )
}
```

可以看出，写出这两种代码的目的，都为了从外部控制组件结构，以至于最大限度提高组件的复用能力。其实很难在不了解组件自身含义时，妄下一个通用的结论，说 “你只要这么写，就能保证任何组件都非常通用”。

比如 `Card` 组件可以将 `title` `extra` 设定为 ReactNode，加上 `children`，其实用性已经足够了：

```javascript
render() {
  return (
    <Container>
      <Title>
        {this.props.title}
        {this.props.extra}
      </Title>
      <Body>
        {this.props.children}
      </Body>
    </Container>
  )
}
```

再比如 `Modal` 也只需要对 `Header` `Footer` `children` 支持 ReactNode 就可以保证足够的通用性。

在业务场景，由于代码修改频率较高，复用性重要程度就没那么高。

## 4. 总结

作者也提到了，高阶组件在某些场景很有用，所以不会完全拒绝使用。

在不为组件做注入的场景下是高阶组件的好场景，利用其生命周期实现权限、埋点，在层级少的时候用作依赖注入也非常方便。

其实程序员在思考这些最佳实践时，与艺术家的思考方式很类似，况且这些最佳实践在不同场景、不同团队，不同项目下都有所侧重，所以不用逮着所谓最完美的实践把代码全部重构，以后也全部用一种风格写代码。就像陶瓷艺术家也不会说：我再也不做彩瓷了，因为白瓷这种颜色非常简约，在我心中是完美的，因此我宁愿一辈子只做白瓷。

这一期也想表达一个积极含义，精读周刊是不会 give up 的！

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