luokuning / blogs

翻译,随笔,以及懒得整理……
81 stars 2 forks source link

整洁的代码 vs 脏乱的代码: React 最佳实践 #11

Open luokuning opened 6 years ago

luokuning commented 6 years ago

并不逐字翻译,原文请点这里

这篇文章主要关注如何编写整洁的代码,以及 ES2015 所带来的一些语法糖是如何帮助我们更容易的实现这一点的。

什么才是整洁的代码?为什么我需要关注它?

整洁的代码,说白了就是风格统一的,编写、阅读、维护起来都非常轻松的代码。所以仅仅能够正常工作的代码并不就是整洁的代码。

当你阅读这篇文章的时候就是一个清理“死”代码(dead code)的好机会。你可以重构、删除被注释掉的代码,这一切都是为了更好的维护性。写代码的时候我们就应该想着:我这么写,六个月之后其他人甚至是我自己能看得懂吗?简而言之,整洁的代码就是你写完后可以很自豪的拿回家展示给你妈看的代码。

作为一个优秀的开发者来说,写整洁的代码是自然而然必须要掌握的东西。

整洁的代码看起来就很舒服

如果一段代码给人的感觉是:“总觉得哪里不对啊”,那么很有可能这段代码的的确确确确实实实实在在是有问题的。好的想法经常会一起出现,不好的也是。所以当你觉得目前正在做的事情就像是要把一个正方形的塞子拼命填进一个圆形的孔里的时候,停下来歇一会,退一步想是不是不应该这么做。90% 的可能你会想出来一个更好的解决办法。(Nine times out of 10, you’ll come up with a better solution)

避免重复

如果你发现有一些类似的代码散落在各个地方,那么考虑提取出一个公共的模式。

// 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>
);

尽管编写公共的模式有时候会导致写更多的代码,但是这样也增加了代码的可维护性,利大于弊。需要注意的时候不要在编写公共模式上陷的太深,知道什么时候该停止。

整洁的代码意味着可预测及可测试

单元测试在前端开发中也已经变得越来越重要,甚至不可或缺,所以确保你的代码是可测试的。

整洁的代码是自注释的(self-commenting)

当你某天写了一段不错的代码,为了其他人能够更方便的阅读,你附上了一段详尽的注释。突然有一天你发现了这段代码里有一个 bug,所以你又修改了你的代码,看起来再正常不过。等等,注释你也一起改了吗?你可能记得,也可能不记得了。下一个维护你这段代码的人可能会因为信任你的注释而直接跳过代码,那么他的结局可能会很惨。

仅仅在有需要的地方加上注释,别注释显而易见的东西,这样能减少视觉上的脏乱。

// Dirty
const fetchUser = (id) => (
  fetch(buildUri`/users/${id}`) // 获取用户的 DTO 记录
    .then(convertFormat) // 转换为下划线的形式
    .then(validateUser) // 确保用户是有效的
);

更干净更合理的注释 (注意 convertFormt 转换为了 snakeToCamelCase):

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

关于命名

// Dirty
const done = current >= goal;
// Clean
const isComplete = current >= goal;
// Dirty
const loadConfigFromServer = () => {
  ...
};
// Clean
const loadConfig = () => {
  ...
};

整洁的代码应该遵守良好的设计模式与最佳实践

这么多年来,程序员们发现了很多能够解决特定问题的方法,也就是我们常说的设计模式。这些设计模式已经被无数次证明能够良好的工作、有针对性的解决某些问题,所以你应该站在巨人的肩膀上,利用好这些设计模式,从而避免自己犯下前人们犯过的错误。

最佳实践跟设计模式类似,但含义更广泛,通常来说并不特指某些算法。比如 “你应该使用 eslint 检验你的代码” 或者 “当写独立的库文件时,你应该把 React 作为 peerDependency” 等这些宽泛的建议。

这里有一些当你编写 React 程序时的最佳实践:

整洁的代码并不意味着需要更长时间来编写

我总是听到这样的说法:编写干净的代码会降低生产力。这种说法基本上是胡说八道。俗话说万事开头难,最开始编写整洁的代码的时候可能会比较慢,但是后面会越来越快,因为你需要写的代码行数比脏乱的代码要少。

如果你的代码能够拆分成许多遵守单一原则的细小模块,那么很可能你写完这些模块之后就不用再碰它们了,“写了就忘了它们吧!”

脏乱的代码 vs 整洁的代码

避免重复

// Dirty
import Title from './Title';
export const Thingie = ({ description }) => (
  <div class="thingie">
    <div class="description-wrapper">
      <Description value={description} />
    </div>
  </div>
);
export const ThingieWithTitle = ({ title, description }) => (
  <div>
    <Title value={title} />
    <div class="description-wrapper">
      <Description value={description} />
    </div>
  </div>
);

Thingie 相当于 ThingieWithTitle 组件删除一个 Title 属性后的组件。我们来写一个公共模式:

// Clean
import Title from './Title';
export const Thingie = ({ description, children }) => (
  <div class="thingie">
    {children}
    <div class="description-wrapper">
      <Description value={description} />
    </div>
  </div>
);
export const ThingieWithTitle = ({ title, ...others }) => (
  <Thingie {...others}>
    <Title value={title} />
  </Thingie>
);

默认值

// Dirty
const Icon = ({ className, onClick }) => {
  const additionalClasses = className || 'icon-large';
  return (
    <span
      className={`icon-hover ${additionalClasses}`}
      onClick={onClick}>
    </span>
  );
};

上面给 className 赋默认值的做法在以前非常常见,这里我们可以用 ES2016 的默认值语法来使代码更简洁:

// Clean
const Icon = ({ className = 'icon-large', onClick }) => (
  <span className={`icon-hover ${className}`} onClick={onClick} />
);

简洁多了。不过我们还可以用一个更简洁更 React 的方式来优化:

// Cleaner
const Icon = ({ className, onClick }) => (
  <span className={`icon-hover ${className}`} onClick={onClick} />
);
Icon.defaultProps = {
  className: 'icon-large',
};

为什么说这种方式更整洁?首先,使用 React 的方式设置默认 props 值是一种性能更好的方式,因为默认 props 是基于组件的生命周期的,同时这样也能够使用 React 的 propTypes 检查机制。另外一点,这种方式把赋默认值的逻辑从组件中抽象了出来。

因此你可以把组件的默认属性值放置在一个单独的模块中,然后作为依赖引入。这里并不是建议你去这么做,只是说这种方式带来的弹性完全能够让你有能力这么做。

import defaultProps from './defaultProps';
...
Icon.defaultProps = defaultProps.Icon;

从渲染中分离状态逻辑 (Separate stateful aspects from rendering)

将有状态的数据加载逻辑与渲染逻辑混合在一个组件中会导致这个组件的复杂度增加。你应该分离两者,将数据加载逻辑放置在容器中,而将渲染放置在另一个组件中,从而使这两个组件遵循单一原则。这就是我们所说的容器模式

在下面的这个例子中,用户的数据加载与渲染都在同一个组件中:

// 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 })})
  }
}

这里我们按照上述说的容器模式进行了分离。这不仅仅是让代码变得更加易读,也让代码更加可测。由于 RenderUser 是一个无状态的组件,因此它的运行结果是可预测的。

使用无状态的组件 (stateless functional components)

React v0.14.0 引入了无状态的组件,这些组件更加简单,只专注于渲染。比如下面这个组件就非常适合转换成无状态组件。

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

无状态组件不仅看起来更简洁,性能也会更好,因为这并不需要创建一个完整的实例。

这属于 ES2015 的语法范畴,很简单,举个栗子就好,不赘述了。

Rest/spread 运算符

// Dirty
const MyComponent = (props) => {
  const others = Object.assign({}, props);
  delete others.className;
  return (
    <div className={props.className}>
      {React.createElement(MyOtherComponent, others)}
    </div>
  );
};
// Clean
const MyComponent = ({ className, ...others }) => (
  <div className={className}>
    <MyOtherComponent {...others} />
  </div>
);

对象解构

// Dirty
componentWillReceiveProps(newProps) {
  this.setState({
    active: newProps.active
  });
}
// Clean
componentWillReceiveProps({ active }) {
  this.setState({ active });
}

数组解构

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

结论…

一旦你坚持编写整洁的代码,那么这会自然而然成为你的习惯,你将会很快享受 “写完就忘了吧!” 所带来的各种好处。希望这篇文章对你有所帮助。