SFC, Redux, HOCなどコンポーネント指向とReact開発のキーワード
CTOの Shoken です。キッチハイクでは2年前にRailsへのReact導入、1年半前に0ベースからReact Nativeでアプリ開発を始めました。この記事では、React, React Nativeで開発しているチームが共通認識したいReactの重要な概念について紹介します。
2018/11/07 追記(はてブコメントより)
Reactリポジトリで名称の変更が行われ、変数名やクラス名が変更されました。いままでの Functional Component
が Function Component
となり、 Stateless
は使わなくなって Function
に統一されるようです。
Terminology: Functional -> Function Component #13775
Before | After |
---|---|
functional component | function component |
StatelessComponent | FunctionComponent |
- SFC, Redux, HOCなどコンポーネント指向とReact開発のキーワード
- 2018/11/07 追記(はてブコメントより)
- SFC ( Stateless Functional Component )
- Flux
- Redux
- Official Context API
- Render Props と HOC ( Higher Order Components )
- Suspense と Hooks
- まとめ
- We’re Hiring!
SFC ( Stateless Functional Component )
KHでの方針: 可能な限りSFCにする
SFC ( Stateless Functional Component ) は状態を持たないコンポーネントです。Reactのコンポーネントを構成する要素にstateとpropsの2つがありますが、そのうちのstateを持たないものです。コンポーネント設計の基本となるので、状態を持たないSFCという考え方はキッチハイクの開発チームでも特に重要視しています。
React の公式ドキュメントには Components and Props で書かれています。
Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen
訳: 概念的には、コンポーネントはJavaScriptの関数のようなものです。任意の入力(props)を受け入れて、何が画面に表示されるべきかが記述された React elements を返します。
ドキュメントの最初に書かれている例が Stateless Functional Component と呼ばれているものです。
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
コンポーネント指向で設計する際にSFCを使うと、振る舞いと表示の責務をコンポーネントレベルで明示的に分けることができます。キッチハイクの開発ではSFCで書けるコンポーネントは可能な限りSFCにする方針で開発しています。可能な限り、stateを使わずにpropsのみを使ったFunctional Componentとして切り出して、コンポーネントを副作用の無い、値をDOMに変換する装置に徹することを目指しています。
また、後述する React Hooks
や Suspense
はSFC + α で状態管理や非同期通信を可能にするような使い方ができるので、今後のReactに追加されるAPIを理解する上でも重要な概念です。
キッチハイクでは以下の書き方をベースにしています。
const KHButton = props => { return ( <View> <KHText style={props.textStyle}>{props.title}</KHText> </View> ); }; KHButton.propTypes = { title: PropTypes.string.isRequired, textStyle: PropTypes.object, }; KHButton.defaultProps = { title: '', textStyle: {}, }; export default KHButton;
メリット
- テスタブルになる
- このコンポーネントでは状態を持たないということが明示的になる
- パフォーマンスが高く、ライフサイクルについて心配する必要がない
Flux
FluxはFacebookが提唱しているデータフローのデザインパターンです。Reactでコンポーネント間のpropsの受け渡しと状態管理を設計する上で、重要な考え方になります。公式ドキュメント にはコンセプトとして以下のように説明されています。
Flux is a pattern for managing data flow in your application. The most important concept is that data flows in one direction.
訳: Fluxは、アプリケーションのデータフローを管理するためのパターンです。 最も重要な概念は、データが一方向に流れることです。
データフロー図
Redux
KHでの方針: 使うべき時期が来るまで使わない
Fluxの実装の一つで、コンポーネントの状態管理を行うライブラリです。3つの設計指針があります。
- Single source of truth
- State in read-only
- Changes are made with pure functions
1. Single source of truth
Storeと呼ばれるものでアプリケーション全体の状態を集中管理します。一つのオブジェクトツリーなので、JSONを考えるとイメージしやすい。
2. State in read-only
State変更のI/Fを限定その1。Stateは常に読み取り専用にして、変更するにはActionを通じてのみ可能です。 Viewからの直接的な変更がないことがライブラリとして保証されていることがメンテナンス性を向上させます。
3. Changes are made with pure functions
State変更のI/Fを限定その2。Reducerと呼ばれるものしかStateを変更できない設計です。ReducerはActionとStateを受け取り、新しいStateを返す関数です。
データフロー
What's Redux and how to use it? Redux Series I にあるGIFが今まで見た中で一番わかりやすいです。
メリット
Reduxの一貫した原則が設計の根幹にあるので、設計にしたがってレールに乗ればReduxの考えるベストプラクティスを自然に実装することになります。ただし、何かを簡単にするというよりは、状態管理に伴う重要な責務が一箇所に集まるようなイメージです。解決したいものはSFCと共通点があり、描画部分と状態管理部分を分けたい、という課題です。Reduxのレールに乗っている限りでは、大規模になっても一貫した設計を提供することが可能になります。
ただし、一方でReactの新しいAPIを見ると、Reduxがカバーしていた範囲の一部を代替するようなものが出てきているような印象を受けます。
Official Context API
KHでの方針: 使える箇所で実験的に使っていく
今まで実験的なAPIとして提供されていたものが React v16.3 で公式リリースされ、ReactのみでReduxが担っていた状態管理機能の部分、いわゆるpropsのバケツリレーを代替できるようになりました。Reduxの全てが代替できるということではなく、あくまでも一部機能の代わりに使えるという位置付けです。
React.createContext() して Consumer とProviderを使うことで、親-子関係以外のコンポーネントに値を渡すことが可能になります。
const ThemeContext = React.createContext('light'); class App extends React.Component { render() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
Render Props と HOC ( Higher Order Components )
KHでの方針: Render Props を採用
コンポーネントに共通の振る舞いを持たせる仕組みとして Render Props と HOC があります。どちらも React の公式ドキュメントで紹介されてますが、キッチハイクでは Render Props に寄せていく方針です。
Render Props
A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.
訳: render という prop を持つコンポーネントは、React element を返す関数を受け取り、独自のレンダリングロジックを実装する代わりにpropsとして受け取ったrender関数を実行します。
<DataProvider render={data => ( <h1>Hello {data.target}</h1> )}/>
値が関数となる prop を使って、React のコンポーネント間でコードをシェアするためのシンプルなテクニックです。 HOCと同じく、カプセル化されたコンポーネント間でライフサイクルメソッド・関数・状態の共有をいかに行うかを解決する一つの方法です。
HOC ( Higher Order Components )
HOC はコンポーネントを引数にとり、付加価値のついたコンポーネントを返す関数です。カプセル化されたコンポーネント間で、ライフサイクルメソッドや状態を変更する関数を共通化し、ロジックを一元管理したコードを再利用することが可能になります。高階関数 ( higher-order function ) をReactのコンポーネント指向の世界で実現したものが、 Higher Order Components。複数のコンポーネントに共通した振る舞いを共通化できるのがメリットで、RubyだとModuleのイメージです。
HOC の構造
以下のかけ算を行うコードを例に考えるとわかりやすいです。関数を返す関数に、引数として何かを渡す、をシンプルに実装するとこのようになります。
const multiply = (x) => (y) => x * y multiply(5)(20) // 100
上記とHOCの違いはこの2つ。
- 戻り値がコンポーネント
- 引数がコンポーネント
HOCコンポーネントのサンプルコードです。
import React, { Component } from "react"; const isEmpty = prop => // do something const Loading = loadingProp => WrappedComponent => { return class LoadingHOC extends Component { componentDidMount() { // do something } render() { const myProps = { loadingTime: ((this.endTimer - this.startTimer) / 1000).toFixed(2) }; return isEmpty(this.props[loadingProp]) ? ( <div className="loader" /> ) : ( <WrappedComponent {...this.props} {...myProps} /> ); } }; }; export default Loading;
次に使い方のサンプルコードです。 export default Loading("names")(List);
という風に Loading
関数が返す LoadingHOC コンポーネントに List
コンポーネントを引数で渡す。
import React, { Component } from "react"; import Item from "./Item"; import Loading from "./HOC/Loading"; class List extends Component { render() { return ( <Item name={this.props.names} /> ); } } export default Loading("names")(List);
Suspense と Hooks
最後に、React v17 に向けて開発されている Suspense と Hooks を紹介します。Reduxの一部代替機能を SFC + α で実現できるようになっていくような印象を受けます。
以下は執筆時の情報ですのでリリース時には変更される可能性があります。
Suspense ( React.lazy, React.Suspense )
React v16.6.0 で実装されている、非同期処理を扱う概念です。React.lazy
で非同期コンポーネント作成、 React.Suspense
で囲むことで非同期処理を行います。
外部APIからデータを取得・表示し、読み込みを開始したらLoadingを出して、APIが戻ってきたらデータを表示する、ローディングを消す、というこれまでRedux(redux-saga)が担当していた責務を代替できる可能性があります。
Hooks
React v16.7.0-alpha で実装されている、HOCの代替として使える機能です。SFC でも状態操作を行う関数( useState )や componentDidMount などのライフサイクル操作が可能になり、SFC + Hooks で状態操作を実現できるようになります。この Pull Request RFC: React Hooks #68 が仕様提案になっているようです。
まとめ
- SFC ( Stateless Functional Component ) を使うことで状態と描画の責務分離を行うことができる
- SFC + αで実現できることが増えてきてSFCは今後もコンポーネントの基本となる(例: Hooks)
- React本家でReduxの一部を代替するようなAPIの開発が進んでいる(例: Context API, Suspense)
あわせてこちらも読んでもらえると嬉しいです。
We’re Hiring!
キッチハイクでは、React Nativeエンジニア・Reactエンジニア・Railsエンジニア・インターンエンジニアを募集中です!