React v16でContext APIが新しくなりました。
このContextを使うことで、これまでサードパーティーのライブラリに頼っていたstate管理をreact本来の機能で実装できるようになりました。
しかしfacebookの例により、シンプルで柔軟性に富む代わりに実際の運用が難しいです。
それではreactのContext APIをstate管理に使う際のメリットデメリットについて書きます。
メリット
- 記述量が圧倒的に少ない。reduxの1/3。
- stateだけでなく、変数を渡せる。
- 可読性が高くシンプルなコードが書ける。
- 学習コストが低い。
- react hookのuseReducerと共に使うとかなりスケールする。
デメリット - ContainerコンポーネントでJSXが純粋なViewでなくなり、ネストが2段深くなる。
- 複数のstoreからcontextを利用しようとすると、記述が増える。
- storeが一つでないのでスパゲッティになりやすい。
- contextを親のstateに入れないと、renderが何回も呼ばれる。(後述)
Context APIの要素
Context APIは三つの要素のみで成り立ちます。
1. React.createContext
後述するProvider, Consumerをペアで作ります。
例 const {Provider, Consumer} = React.createContext;
// 以下のように名前をつけることもできます。
const RootCotext = React.createContext;
2. Provider
Consumerに、context(state)を渡す為のコンポーネントです。
reduxのprovider, createStoreの役割です。
3. Consumer
Providerからcontext(state)を受け取ります。
reduxのconnect,mapStateToProps,mapDispatchToPropsの役割です。
それでは実例を見てみましょう。
Text はReactNativeの要素で文字列を表示します。
従来はこのようにpropsをバケツリレーして渡していました。
// App.js
const Grandson = (props) => (
<Text>{props.name}</Text>
);
const Son = (props) => (
<Grandson name={props.name}/>
);
export default class Father extends Component {
render() {
return (
<Son name='カバ' />
);
}
次にContextを使って擬似global stateを実現してみます。
// App.js
import React, {Component} from 'react'
import {Text} from 'react-native'
// contextの作成。
const RootContext = React.createContext()
const Grandson = () => (
<RootContext.Consumer>
{context=>
<Text>{context.name}</Text>
}
</RootContext.Consumer>
)
const Son = () => (
<Grandson />
)
export default class Father extends Component {
render() {
return (
<RootContext.Provider value={{name: 'kaba'}}>
<Son />
</RootContext.Provider>
)
}
}
これで父コンポーネントから孫コンポーネントへstate(今回は変数)を渡すことができるようになりました。
このように擬似global変数を作れました。
ただ一つ注意点があって、valueに、オブジェクトを直に渡すと、画面描画時に、毎回オブジェクトが生成されメモリの無駄なので、
export default class Father extends Component {
state = {name: 'kaba'}
render() {
return (
<RootContext.Provider value={this.state}>
<Son />
</RootContext.Provider>
)
}
}
のように、contextはstateの中に入れておくとパフォーマンスの問題が起こりません。
結論
小規模アプリの開発には向いている。
非同期もasync, awaitで解決できそうだ。
combineReducerもどきも独自ルールで作れる。
しかし、それ以外の用途ではredux, mobxを使用した方が使い勝手がいい。
結局シンプルすぎて、独自ルールを追加しないとやっていけないのが辛い。
チーム開発では推奨できない。
だだし、疎結合なので、reduxへの移行も比較的簡単だ。
十分に実用的なので、個人では使っていきたい。
実務で使う場合は、以下の記事を参考にされたし!
React v16で実装された new Context APIを使って、Reduxへ別れを告げる②
Unstated
実は、このContext Apiを超薄く拡張し、実用レベルにまでしたライブラリがある。
unstatedだ。
reduxを捨ててunstatedを使う理由
2019年からreactを使う人はこれを使うといい。star5000越えで、今reactで一番人気のあるreduxの代用だ。
unstatedはわずか200行のライブラリだ。テストもしやすいのでベテランエンジニアにこそオススメしたい。
私は実務で使っている。