本期精读的文章是：[React 代码整洁之道](http://americanexpress.io/clean-code-dirty-code/)。

## 1 引言

编程也是艺术行为，当我们思考代码复用、变量命名时，就是在进行艺术思考。

可能这篇文章没法提高面试能力、开发效率，因为涉及的内容都是 “软能力”。但如果与我一样，时常害怕自己代码不够优雅，那就在茶余饭后看看这篇文章，也许，可以解决一部分你心中的困惑。

## 2 内容概要

作者整理了几个好的思维习惯，尝试认同它，再看看如何实践。

### 不冗余

避免重复代码段，对 JSX 同理：

```javascript
// Dirty
const MyComponent = () => (
  <div>
    <OtherComponent type="a" className="colorful" foo={123} bar={456} />
    <OtherComponent type="b" className="colorful" foo={123} bar={456} />
  </div>
);

// Clean
const MyOtherComponent = ({ type }) => (
  <OtherComponent type={type} className="colorful" foo={123} bar={456} />
);
const MyComponent = () => (
  <div>
    <MyOtherComponent type="a" />
    <MyOtherComponent type="b" />
  </div>
);
```

但也不要过度优化，过度优化和搞破坏没什么区别。

### 可预测、可测试

如果使用 [Jest](https://facebook.github.io/jest/) 测试，可以考虑截图测试插件：[Jest Image Snapshot](https://github.com/americanexpress/jest-image-snapshot)

### 自我解释

尽可能减少代码中的注释。可以通过让变量名更语义化、只注释复杂、潜在逻辑，来减少注释量，同时也提高了可维护性，毕竟不用总在代码与注释之间同步了。

```javascript
// Dirty
const fetchUser = (id) => (
  fetch(buildUri`/users/${id}`) // Get User DTO record from REST API
    .then(convertFormat) // Convert to snakeCase
    .then(validateUser) // Make sure the the user is valid
);

// Clean
const fetchUser = (id) => (
  fetch(buildUri`/users/${id}`)
    .then(snakeToCamelCase)
    .then(validateUser)
);
```

上面的例子，方法 `convertFormat` 含义是 “转换格式”，太过于笼统，以至于不得不添加注释。如果换成 `snakeToCamelCase` （转换为驼峰风格），这个名字就解释了自己的功能。

### 斟酌变量名

> 布尔值或者返回值是布尔类型的函数，命名以 `is` `has` `should` 开头：

```javascript
// Dirty
const done = current >= goal;
// Clean
const isComplete = current >= goal;
```

> 函数以其效果命名，而不是怎么做的来命名

```javascript
// Dirty
const loadConfigFromServer = () => {
  ...
};
// Clean
const loadConfig = () => {
  ...
};
```

很多时候我也经常犯这种错误，毕竟写代码的时候总要考虑实现，一不小心就将实现的方式带入了函数名中。

### 遵循设计模式

这里的设计模式，并不是指工程上的，而是更广泛的开发中的设计模式，比如 “你应该使用 tslint 校验代码格式” “typescript 开启 stricts 模式” “编写一个 React 函数，应当将 React 作为 `peerDependency`” 等等（当然，不要随意设置 `peerDependency` 也是一种江湖约定）。

对于 React，遵循以下几个最佳实践：

- 单一责任原则, 确保每个功能都完整完成一项功能，比如更细粒度的组件拆分，同时也更利于测试。
- 不要把组件的内部依赖强加给使用方。
- lint 规则尽量严格。

根据我的体验，尤为痛恨违背第二条的组件，**比如当 React 组件使用了数据流，但必须依赖项目初始化该数据流才能执行，如果不是被生活所迫，我才不会使用这种组件。**

第三条也一样，**如果你是一个知名轮子的作者，请毫不留情的使用最严格的 lint 规则。如果使用者的 lint 规则比你还严格，你的组件将无法使用。**

### 考虑到以上几点并不会降低编码速度

编写整洁的代码在开始一定会放慢开发速度，因为你需要转变自己的思维模式，但随着不断迭代，它的带来的效率提升会逐渐弥补前面的损失，并不断带来开发效率的提升。

写组件库也是同理，用脚写固然能快速完成，但后续往往要重构掉。我很羡慕函数式工作环境的开发者，他们几乎只要为每个功能写一遍，剩下的就是记住并调用它。

### 在 React 中的实践

略过几个没意思的例子。。

> 在 React 使用 defaultProps 代替在代码中动态判断

显然，利用 React 组件的规则，将入参的默认值预先定义好是最高效的。但顺带一句，如果在 ts 最严格的 `stricts` 模式里，依然会报错：变量可能未定义。这是因为 `defaultProps` 依然是个约定，而不能通过强类型推导出，目前还没有更优雅的解决思路。

> 渲染与判断逻辑分开

```javascript
// Dirty
class User extends Component {
  state = { loading: true };

  render() {
    const { loading, user } = this.state;
    return loading
      ? <div>Loading...</div>
      : <div>
          <div>
            First name: {user.firstName}
          </div>
          <div>
            First name: {user.lastName}
          </div>
          ...
        </div>;
  }

  componentDidMount() {
    fetchUser(this.props.id)
      .then((user) => { this.setState({ loading: false, user })})
  }
}

// Clean
import RenderUser from './RenderUser';
class User extends Component {
  state = { loading: true };

  render() {
    const { loading, user } = this.state;
    return loading ? <Loading /> : <RenderUser user={user} />;
  }

  componentDidMount() {
    fetchUser(this.props.id)
      .then(user => { this.setState({ loading: false, user })})
  }
}
```

逻辑与渲染分离，便于维护，其次便于测试。

当然有人可能会问 “就算逻辑与渲染分离了，但组成的大组件不还是逻辑耦合的吗”，对，这就像函数单一指责一样，`render` 是过程代码，但过程中涉及到的逻辑，分配给单一指责的渲染组件渲染，如果把逻辑与渲染写在一起，就类似一个函数把功能全做完，这样做显然诸事不利。

### 提倡无状态组件

```javascript
// Dirty
class TableRowWrapper extends Component {
  render() {
    return (
      <tr>
        {this.props.children}
      </tr>
    );
  }
}

// Clean
const TableRowWrapper = ({ children }) => (
  <tr>
    {children}
  </tr>
);
```

性能是一个原因，原文比较强调性能与代码量。我认为 `stateless` 重点在于阻碍了内部状态的使用，移除了生命周期，所以提高了组件的可控性，也就拓宽了组件的使用场景。

受控与非受控组件都有其适用场景，像非常基础的底层组件库，往往倾向提供两套机制，通过 `value` 与 `defaultValue` 决定是否受控。拥有这样能力的组件源码就没法通过 `stateless` 写，所以无状态组件的面向对象并不是基础底层组件，而且这些基础组件也没必要完全无状态，两者都提供是最好的选择。

说到这，也就是考虑到成本问题，那么无状态组件也就更适合上层具有业务含义的组件。页面级别组件状态太多，不适合，所以我认为无状态组件比较适合 `Wrapper` 层，也就是对基础组件包裹并增强业务能力这一层。

### 解构

```javascript
// Dirty
const splitLocale = locale.split('-');
const language = splitLocale[0];
const country = splitLocale[1];

// Clean
const [language, country] = locale.split('-');
```

ES6 新增的语法可以提升不少代码可读性，需要刻意训练去培养这个习惯。

## 3 精读

本周精读已经融于内容概要中 ^_^。最后推荐在 typescript 中开启 `strict` 模式，强制使用良好的开发习惯。

```javascript
// Bad
onChange(value => console.log(value.name))
// Dirty
onChange((value) => {
  if (!value) {
    value = {}
  }
  console.log(value.name)
})
// Clean
onChange((value = {}) => console.log(value.name))
// Clean
onChange(value => console.log(value?.name))
```

不要信任任何回调函数给你的变量，它们随时可能是 `undefined`，使用初始值是个不错的选择，但有的时候初始值没什么意义，使用 `?.` 语法可以安全的访问属性，是时候抛弃 `_.get` 了。

## 4. 总结

我要回去重构代码了，你呢？

### 更多讨论

> 讨论地址是：[精读《React 代码整洁之道》 · Issue #46 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/46)

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