2017末時点での React Component 設計のベストプラクティス

どう考えているか、というのを聞かれたので、記事に起こしておきます。個人の意見です。

Prettier を使う

https://github.com/prettier/prettier

気づけばコードの整形を人間がやる時代は終わりました。
細かいコーディングスタイルでレビューの時間を取るぐらいだったら、一貫した自動整形ルールを適用すべきです。

人によっては細かいこだわりがあって prettier の規則が気に食わないかもしれず、僕も最初はそうでしたが、Atomで保存する度に自動整形を走らせる prettier の強烈な開発体験によって、最終的にそれらのこだわりを全て捨てることが出来ました。

生産性を求めるなら、現時点では最優先で導入すべきものです。

React.createClass を使わない

v16 で削除されたのでいわずもがな。
同様に、 createClass でしか使えなかった mixin 周辺機能も丸ごと deprecated です。

「可能な限りは」 Stateless Fuctional Component を使う

悪い例

これはアンチパターンとは言い切れないけど、今ではあまりいいコンポーネント定義ではない例です。

class App extends React.Component {
  componentDidMount() {
    console.log('mounted')
  }
  render() {
    return <span>Hello</span>
  }
}

理由

クラスベースの設計は状態を内部で抱えがちで、React.Component の内部においては state は好まれません。プレゼンテーショナルなレイヤーだということを明示するために、state に触らない限りは extends React.Component は推奨しません。

現実的には、state は完全に排除出来ないとは思いますが、 state を持つ component は特殊なコンテナーであって、出来る限り抑えるべきです。

それを機械的に検出するのに eslint-plugin-react/prefer-stateless-function をおすすめしています。

https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md

代替

HOC + Functional Stateless Component

とりあえずは recompose を使うのを推奨しています。

import { lifecycle } from 'recompose'

lifecycle({
  componentDidMount() {
    console.log('hello')
  }
})(function App () {
  return <span>Hello</span>
})

複数の HOC を使う場合は compose を使います。

import { compose, lifecycle, pure } from 'recompose'

compose(
  lifecycle({
    componentDidMount() {
      console.log('hello')
    }
  }),
  pure
})(function App () {
  return <span>Hello</span>
})

redux を使っているならば、 コールバックから dispatch することはあっても、状態そのものは component 内部に残らないはず。
結果として、 extends する React Component はほとんどいなくなるはずです。

内部関数で子Component を render しない

悪い例

よく見る駄目な例。

class Layout extends React.Component {
  renderHeader() {
    return <header/>
  }
  render() {
    return <div>
      {this.renderHeader()}
      {this.props.children}
      </div>
  }
}

良い例

責務に則って分割します。

function Header() {
  return <header/>
}

function Layout({children}) {
  return <div>
    <Header/>
    {children}
  </div>
}

本当に必要になるまで ImmutableJS を使わない

理由

これは議論の余地があるとは思っているのですが、自分の経験上、 Immutable JS は、一部のドメインを無駄にややこしくするだけで、理解せずに使うとパフォーマンス問題を解決するツールなのに重篤なパフォーマンス問題を引き起こしがちで、そしてほとんどの人はこれを使う意味を理解していません。とくにチーム開発においては、百害あって一理なしだと思っています。

代替

ES2015 の object-rest-spread を使う

const prev = { a: 1, b: 1 }
const next = { ...prev, b: 2}

Reactの差分検知で、オブジェクト参照が同じままその内部状態を抱えてしまうと、変更検知されない可能性があります。

もっと攻めたルールだと、React に限らずオブジェクトのメンバへの再代入禁止、もありえます。あらゆるオブジェクトは暗黙にイミュータブルとして扱うというわけです。自分の個人プロジェクトはそれで運用して、ちゃんと回っています。

PropTypes はもう使わない

理由

shape や required といった特殊な記法を覚えさせられるのに、ランタイムチェックしか出来ない、というのは静的解析ツールが充実した今では、二重定義になり足枷になりつつあります。

おそらくですが、Facebookの中の人達はもう使ってないです。古い時代の名残で残ってる機能のように見えます。この機能に限らず、Reactの機能が拡充される方向性は、Facebook社自身の内部での使い方に強く依存します。

代替案

Flow / TypeScript を使ってください。どちらも JSX の props の型情報を静的に検証できます。

Action定義では Flux Standard Action に従う

React からやや外れますが、Action 定義で FSA に準拠していない人が多いです。

https://github.com/acdlite/flux-standard-action

一部の redux middleware は正しくFSAに準拠しないとちゃんと動かなかったりします。

悪い例

{
  type: 'move-to',
  x: 1,
  y: 3
}

良い例

{
  type: 'move-to',
  payload: {
    x: 1,
    y: 3
  }
}

まとめ

  • Prettier を使う
  • Stateless Component を優先する
  • プレゼンテーショナルでない振る舞いの定義は HOC で行う
  • PropTypes より flow/typescript を使う
  • FSAに従う
  • JSのオブジェクトをイミュータブルだと思い込んで使う