整洁代码 or 糟糕代码: React 最佳实践

344236782 6年前
   <p>本文将重点介绍适用于现代 React 软件开发的整洁代码实践。我还会谈谈 ES6/ES2015 带到台面上的一些“糖”。</p>    <h3><strong>什么是整洁代码?为什么我在乎?</strong></h3>    <p>整洁代码是一种一致的编程风格,使你的代码更易于编写,阅读和维护。开发人员通常花费时间解决问题,一旦问题解决,他们就会提出 pull 请求。我认为你不仅仅因为代码“工作”就完事。</p>    <p>现在是去清理代码的好时机,通过删除死代码(僵尸代码)、重构和删除任何注释掉的代码! 为了可维护性而努力。问问自己:“从现在开始六个月后,其他人能够理解这段代码吗?”</p>    <p>简单地说,写你会很自豪地带回家并展示给母亲看的代码。</p>    <p>你为什么在乎?因为如果你是一个优秀的开发人员,那么你很懒。请听我解释 – 那是一句褒扬的话。一个优秀的开发人员,在遇到不止一次需要做某事的情况下,通常会找到一个自动化(或更好的)解决方案来完成手头的任务。所以,因为你很懒,认同整洁代码的技巧将会减少来自 pull 请求代码评审的变化频率,以及减少一遍又一遍地回到同一段代码的频率。</p>    <h3><strong>整洁代码通过“气味测试”</strong></h3>    <p>整洁的代码应该通过气味测试。这是什么意思?我们都看过代码(我们自己的或别人的)并说:“这里不太对劲。” 记住,如果觉得它不对劲,那很可能就是不对劲。好的代码都是深思后一起来的。如果你觉得你正在试图把一个方形的钉子钉进一个圆形的洞里,那么就暂停一下,后退一步,然后休息一下。在多次尝试后,你会想出一个更好的解决方案。</p>    <h3><strong>整洁的代码是 DRY (不要重复自己)</strong></h3>    <p>DRY 是代表“不要重复自己”的首字母缩略词。如果你在多个地方做同样的事情,请合并重复的代码。如果你在你的代码中看到了模式(复写),那是表示应该进行 DRY 的重要迹象。有时这意味着从显示屏退后,直到你无法看清文本,然后从字面上寻找模式。</p>    <pre>  <code class="language-javascript">// Dirty  const MyComponent = () => (    <div>      <OtherComponent type="a" className="colorful" foo={123} bar={456} />      <OtherComponent type="b" className="colorful" foo={123} bar={456} />        </div>  );</code></pre>    <pre>  <code class="language-javascript">// Clean  const MyOtherComponent = ({ type }) => (    <OtherComponent type={type} className="colorful" foo={123} bar={456} />  );  const MyComponent = () => (    <div>      <MyOtherComponent type="a" />      <MyOtherComponent type="b" />    </div>  );</code></pre>    <p>有时候,正如我们上面的实例,对代码进行 DRY 实际上可能会增加代码的规模。然而,对代码进行 DRY 通常也会提高可维护性。</p>    <p>请注意,对代码进行 DRY 可能会过了头。因此,必须懂得适可而止。</p>    <h3><strong>整洁的代码是可预料和可测试的</strong></h3>    <p>写单元测试不仅仅只是一个好主意,还几乎应该成为强制性的。毕竟你怎能确保你的新功能没在其它地方引入 BUG 呢?</p>    <p>许多的 React 开发者依靠 Jest 的 zero-configuration 测试运行器,并生成代码覆盖率报告。如果你对视觉上的前后对比测试感兴趣,请查看美国运通公司自有的 <a href="/misc/goto?guid=4959755562778450129" rel="nofollow,noindex">Jest Image Snapshot</a> 。</p>    <h3><strong>整洁的代码是自我注释的</strong></h3>    <p>你曾遇过这种情况吗?你写了些代码而且确保它有充分的注释。后来你发现了一个 BUG ,因此你倒回去修复代码,但你有记得更改注释去反映出最新的逻辑吗?可能有也可能没有。下一个人去看你的代码时可能会因为你的注释而掉入一个陷阱。</p>    <p>添加注释只是为了解释复杂的想法/逻辑。也就是不需要为显而易见的代码注释了。可以减少视觉上的杂乱。</p>    <pre>  <code class="language-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  );</code></pre>    <p>在整洁版上我们重命名了一些方法去更好地描述它们是干嘛的,因此消除了原本需要的注释且减少了视觉上的混乱。并且限制了未来潜在的代码与注释不匹配的混乱。</p>    <pre>  <code class="language-javascript">// Clean  const fetchUser = (id) => (    fetch(buildUri`/users/${id}`)      .then(snakeToCamelCase)      .then(validateUser)  );</code></pre>    <h3><strong>给事物命名</strong></h3>    <p>在我之前的文章 <a href="/misc/goto?guid=4959755562883884940" rel="nofollow,noindex">作为子组件的函数是一种反模式</a> 中,我强调了给事物命名的重要性。我们都应该认真考虑变量名、函数名,甚至文件名。</p>    <p>以下是一些指导原则:</p>    <ul>     <li> <p>布尔变量或返回布尔值的函数,应该以 “is”、 “has” 或 “should”开头。</p> <pre>  <code class="language-javascript">// Dirty  const done = current >= goal;</code></pre> <pre>  <code class="language-javascript">// Clean  const isComplete = current >= goal;</code></pre> </li>     <li> <p>函数的命名应该是它们能做的,而不是它们如何做的。换句话说,不要在命名中公开实现的细节。为什么?因为你怎么做有一天可能会改变,而你不应该因为它重构你的调用代码。例如,今天你可能会从 REST API 加载你的配置,但是你可能决定明天将其并入 JavaScript 中。</p> <pre>  <code class="language-javascript">// Dirty  const loadConfigFromServer = () => {    ...  };</code></pre> <pre>  <code class="language-javascript">// Clean  const loadConfig = () => {    ...  };</code></pre> </li>    </ul>    <h3><strong>整洁代码遵循成熟的设计模式和最佳实践</strong></h3>    <p>计算机已经存在了很长的一段时间。多年来,程序员通过解决某些问题,发现了模式。并称之为设计模式。换句话说,有些算法已经被证明是可以工作的,所以你应该站在那些在你之前的人的肩膀上,这样你就不必犯同样的错误了。</p>    <p>之后有了最佳实践。它们与设计模式类似,但是更广泛,它们不是针对编码算法。他们可能是诸如“你应该用 Lint 优化你的代码” 或者 “当编写一个库包时,包含 React 作为一个 peerDependency” 这些做法。</p>    <p>构建 React 应用程序时,请遵循以下最佳实践。</p>    <ul>     <li> <p>使用函数,每个函数都有一个功能。 这就是所谓的单一责任原则。 确保每个功能都能完成一项工作,并做得很好。 这可能意味着将复杂的组件分解成许多较小的组件。 这也能使得测试更容易。</p> </li>     <li> <p>留心观察脆弱的抽象。换句话说,就是不要将您的内部需求强加给您的代码使用者。</p> </li>     <li> <p>遵循严格的 Lint 化规则。这将帮助您编写整洁,一致的代码。</p> </li>    </ul>    <h3><strong>整洁代码不(一定)要花更长的时间去写</strong></h3>    <p>我总是听到这样的说法:编写整洁的代码会降低生产力。这简直是胡扯。是的,最初你可能需要放慢速度然后才能加速,但是最终你的步伐会随着你写更少的代码而加快。</p>    <p>不要低估“重写因子”以及修正来自代码评审的意见所花的时间。如果你把代码分成几个小模块,每个模块都有一个单独的职责,那么很有可能你再也不需要碰触大多数模块了。在“写代码然后忘记它”上你会节约时间。</p>    <h3><strong>脏代码与整洁代码实例</strong></h3>    <p>DRY 化这些代码</p>    <p>让代码看起来像下面的例子中那样。就像我前面提到的,你前进一步处理或后退一步处理都来自于你所看到的。你有看到什么模式了么?注意组件 Thingie 与 ThingieWithTitle 是类似的,除了 Title 组件。这种代码就非常适合 DRY 化改造。</p>    <pre>  <code class="language-javascript">// 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>  );</code></pre>    <p>这里,我们允许通过 children 来访问 Thingie。当我们创建 ThingieWithTitle 的时候,ThingieWithTitle 包装了 Thingie ,把 Title 当做了 children。</p>    <pre>  <code class="language-javascript">// 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>  );</code></pre>    <h3><strong>默认值</strong></h3>    <p>看看下面代码段。它将 className 默认设置为 “icon-large” ,使用逻辑或(OR)语句,类似于你祖父可能做过的方式。</p>    <pre>  <code class="language-javascript">// Dirty  const Icon = ({ className, onClick }) => {    const additionalClasses = className || 'icon-large';    return (      <span        className={`icon-hover ${additionalClasses}`}        onClick={onClick}>      </span>    );  };</code></pre>    <p>这里我们使用 ES6 默认语法来替代空字符串表示的未定义值。这允许我们使用 ES6 中的 fat-arrow 函数的单语句,这就消除了对 return 语句的需求。</p>    <pre>  <code class="language-javascript">// Clean  const Icon = ({ className = 'icon-large', onClick }) => (    <span className={`icon-hover ${className}`} onClick={onClick} />  );</code></pre>    <p>在这个更简洁的版本中,React 是自动设置默认值的。</p>    <pre>  <code class="language-javascript">// Cleaner  const Icon = ({ className, onClick }) => (    <span className={`icon-hover ${className}`} onClick={onClick} />  );  Icon.defaultProps = {    className: 'icon-large',  };</code></pre>    <p>为什么是这个清理器呢?并且它真的很好嘛?难道这三个版本不是做类似的事情吗?对大多数情况而言,答案是肯定的。React 支持设置 prop 默认值的优势是这样可以产生更高效的代码,在基于生命周期组件的类中设置默认 prop 的值,同时支持你的默认值通过 propType 检查。但这还有另一个优势:它把默认值逻辑从组件自身中分离出来。</p>    <p>例如,你可以这么做,将你所有的默认 prop 保存到一个地方。我并不是推荐你这样做;我只是说你可以有这么做的变通选择。</p>    <pre>  <code class="language-javascript">import defaultProps from './defaultProps';  ...  Icon.defaultProps = defaultProps.Icon;</code></pre>    <h3><strong>从渲染分离有状态的部分</strong></h3>    <p>将有状态的数据加载逻辑与渲染(或展示)逻辑混合可能导致组件的复杂性。反之,写一个专门负责加载数据的有状态的容器组件,然后写一个专门负责展示数据的组件。这被称为 <a href="/misc/goto?guid=4959645786337460505" rel="nofollow,noindex">容器模式</a> 。</p>    <p>在下面的示例中,用户数据在单个组件中加载和展示。</p>    <pre>  <code class="language-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 })})    }  }</code></pre>    <p>在整洁的版本中,关注点(加载数据、展示一个加载图标,以及展示数据)已经分离。这不仅使代码更容易理解,也减少了测试的工作量,因为可以独立测试每个关注点。而且由于 RenderUser 是一个无状态的功能组件,结果是可预测的。</p>    <pre>  <code class="language-javascript">// 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 })})    }  }</code></pre>    <p>使用无状态函数组件</p>    <p>无状态函数组件 (SFCs) 是在 React v0.14.0 引入的,用来简化仅渲染组件编写。有些开发者思维还没有转过来,例如下面组件是可以很容易的转化为一个 SFC 组件的:</p>    <pre>  <code class="language-javascript">// Dirty  class TableRowWrapper extends Component {    render() {      return (        <tr>          {this.props.children}        </tr>      );    }  }</code></pre>    <p>该例子的简洁版本去掉了很多糟糕版本的无用代码。由于没有创建实例,通过 React 内核优化,可以节省很多内存:</p>    <pre>  <code class="language-javascript">// Clean  const TableRowWrapper = ({ children }) => (    <tr>      {children}    </tr>  );</code></pre>    <p> </p>    <p>来自:https://www.oschina.net/translate/clean-code-dirty-code</p>    <p> </p>