概要:コンポーネント間で共有する必要がある状態を抽出し、特定の規約に従って統一的に管理し、状態の変化を予測可能にする
Store パターン#
状態を外部変数に保存する、 this.$root.$data
var store = {
state: {
message: 'こんにちは!'
},
setMessageAction (newValue) {
// 変更があった場合はログを記録するなど
this.state.message = newValue
},
clearMessageAction () {
this.state.message = ''
}
}
store の state 値の変更方法(mutation)は action をトリガーすることによってのみ操作されるため、state の変更フローを簡単に追跡でき、エラーが発生した場合もログを通じてエラーの位置を明確にすることができる
storeはコンポーネントがstateをactionを通じてのみ変更することを制限していない
この点から Flux アーキテクチャが進化した
Flux#
Flux は MVC や MVVM のような考え方で、アプリケーションを 4 つの部分に分ける
——View、Action、Dispatcher、Store
View 層は vue や react などのフレームワークを通じて実現でき、View 内のデータはすべて Store にあり、Store が変更されるとイベントが発生し、すべての購読者(またはリスナー、異なるフレームワークに応じて異なるデータ応答技術)に変更を通知する
Flux では、View が Store を変更するためには一連のプロセスを経る必要がある
- View はまず Dispatcher に通知し、Dispatcher に action を dispatch させる
- Dispatcher は View から発信された action を受け取り、それを Store に転送する
- Store は対応する action をトリガーしてデータを更新する
- データの更新は View の更新を伴う
注意:
- Dispatcher の役割はすべての Action を受け取ること。次にすべての Store に送信する(Action は View からトリガーされることもあれば、他の場所からトリガーされることもある、例えばテストケース)
- Store の変更は Action を通じてのみ行われ、Store は公開の Setter を持つべきではなく、すべての Setter はプライベートであるべきで、公開の Getter のみを持つべきである
- 具体的な Action の処理ロジックは一般的に Store 内に置かれる
Flux の特徴: 単方向の流れ
Redux#
Flux の考え方に似ているが、Flux のいくつかの特性を変更した:
- アプリケーションは複数の Store を持つことができる
- 複数の Store 間に依存関係が存在する可能性がある
- Store はデータ処理のロジックもカプセル化している
Store#
Redux には 1 つの Store しかなく、アプリケーション全体のデータはこの大きな Store に存在する。Store の State は直接変更できず、毎回新しい State を返す必要がある。Redux は createStore 関数を用意して Store を生成する。
import { createStore } from 'redux';
const store = createStore(fn);
Store は store.subscribe メソッドを使用してリスナ関数を設定することを許可し、State が変更されると自動的にこの関数が実行される。これにより、View が何を使って実装されていても、View の更新関数を subscribe するだけで、State の変化後に View が自動的にレンダリングされる。例えば、React ではコンポーネントの render メソッドや setState メソッドを subscribe すればよい。
Action#
Flux と同様に、Redux にも Action があり、Action は View から発信される通知で、Store に State が変更されることを知らせる。Action には type 属性が必要で、Action の名前を表し、他に一連の属性を設定して State 変更時の参考にする。
const action = {
type: 'ADD_TODO',
payload: 'Reduxを学ぶ'
};
Redux は Action Creator を使用して一括で Action を生成することができる。
Reducer#
Redux には Dispatcher の概念がなく、Store 内に dispatch メソッドが統合されている。store.dispatch () は View が Action を発信する唯一の方法である。
import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
type: 'ADD_TODO',
payload: 'Reduxを学ぶ'
});
Redux は Reducer という純粋関数を使用してイベントを処理する。Store が Action を受け取った後、必ず新しい Stateを返さなければならない(つまり、Store の State は直接変更できず、毎回新しい State を返す必要がある)、これにより View が変化する。この State の計算プロセスを Reduce と呼ぶ
純粋関数とは、副作用がない関数のこと
- 同じ入力に対しては常に同じ出力を返す
- 外部の変数に影響を与えず、外部の変数に影響を受けない
- 引数を変更しない
Redux はアプリケーションの状態と現在の action に基づいて新しい state を導き出す:(previousState, action) => newState
Flux と比較すると:(state, action) => state
question: なぜReducerと呼ばれるのか?
—— reduceは関数型プログラミングの概念で、mapと一緒に語られることが多い。簡単に言うと、mapはマッピング、reduceは要約である。
マッピングはリストを一定のルールに従って別のリストに変換することであり、reduceはリストを一定のルールに従って統合すること、または初期値に対して一連の操作を行い、新しい値を返すことと理解できる
全体の流れ#
1、ユーザーが View を通じて Action を発信する:
store.dispatch(action);
2、その後 Store は自動的に Reducer を呼び出し、現在の State と受け取った Action の 2 つのパラメータを渡す。Reducer は新しい State を返す。
let nextState = xxxReducer(previousState, action);
3、State に変更があった場合、Store はリスナ関数を呼び出す。
store.subscribe(listener);
4、リスナは store.getState () を使用して現在の状態を取得できる。React を使用している場合、この時に View を再レンダリングすることができる。
function listerner() {
let newState = store.getState();
component.setState(newState);
}
Flux との比較#
Flux と比較すると:Flux では Store はそれぞれ独立しており、各 Store は対応する View にのみ責任を持ち、更新のたびに対応する View にのみ通知する:
Redux では各子 Reducer はルート Reducer によって統一的に管理され、各子 Reducer の変更はルート Reducer の統合を経る:
簡単に言うと、Redux には 3 つの大原則がある: 単一のデータソース:Flux のデータソースは複数である。 State は読み取り専用:Flux の State は自由に変更できる。 * 純粋関数を使用して変更を実行する:Flux の変更を実行するのは必ずしも純粋関数ではない。
Redux と Flux はどちらも単方向のデータフローである
React との関係#
Redux は Flux に似ているが、単なる考え方や規範であり、React との関係はない。Redux は React、Angular、Ember、jQuery、さらには純粋な JavaScript をサポートしている。
しかし、React は関数型の考え方を含んでおり、単方向のデータフローであるため、Redux と非常に相性が良い。そのため、一般的には Redux を使用して状態管理を行う。Redux と React の UI のバインディングを簡単に処理するために、一般的には react-redux というライブラリを使用して React と組み合わせる。このライブラリは React の公式に提供されたものである(react-redux を使用しない場合、手動で Redux と UI のバインディングを処理する必要があり、多くの重複したコードを書く必要があり、エラーが発生しやすく、UI レンダリングロジックの最適化も必ずしも適切に処理できるわけではない)。
Redux は React コンポーネントをコンテナコンポーネントと表示コンポーネントに分け、コンテナコンポーネントは一般的に connect 関数を通じて生成され、グローバル状態の変化を購読し、mapStateToProps 関数を通じてグローバル状態をフィルタリングすることができる。一方、表示コンポーネントは直接グローバル状態からデータを取得せず、そのデータは親コンポーネントから供給される。
もしコンポーネントが UI の表示とビジネスロジックの処理の両方を必要とする場合、それを分割し、コンテナコンポーネントが表示コンポーネントを包む必要がある。
Redux-saga#
Redux は非同期操作を処理し、中間ウェアを追加した結果である
公式ドキュメント:Redux-Saga
Dva#
公式定義:dva はまずreduxとredux-sagaに基づくデータフローソリューションであり、開発体験を簡素化するために、dva はさらにreact-routerとfetchを内蔵しているため、軽量なアプリケーションフレームワークとも理解できる
簡単に言うと、react-redux と redux-saga を使用して書かれたコードをより合理的に整理し、メンテナンスしやすくすることを目的としている
以前、redux、react-redux、redux-saga などの概念について話したとき、皆さんは頭が混乱しているに違いない。action、reducer、saga など、機能を書くためにこれらの js ファイルの間を行き来しなければならなかった。dva が行うことは非常にシンプルで、これらのものを一緒に書けるようにすることで、分けて書く必要がなくなる。
例えば:
// 以前の書き方はsagas/products.js、reducers/products.js、actions/products.jsを作成し、saga、action、reducerなどを分けて書き、行き来していた
app.model({
// namespace - reducerがrootReducerにcombineされる際のkey値に対応
namespace: 'products',
// state - reducerのinitialStateに対応
state: {
list: [],
loading: false,
},
// subscription - domがreadyになった後に実行
subscriptions: [
function(dispatch) {
dispatch({type: 'products/query'});
},
],
// effects - sagaに対応し、使用を簡素化
effects: {
['products/query']: function*() {
yield call(delay(800));
yield put({
type: 'products/query/success',
payload: ['ant-tool', 'roof'],
});
},
},
// reducers - 伝統的なreducers
reducers: {
['products/query'](state) {
return { ...state, loading: true, };
},
['products/query/success'](state, { payload }) {
return { ...state, loading: false, list: payload };
},
},
});
MobX#
公式サイト:MobX 中文文档
Flux システムの単方向データフローの考え方に対して、MobX の考え方は:アプリケーションの状態に由来するものはすべて自動的に取得されるべきである —— 状態が変わると、状態を使用している他のすべての場所も自動的に変わる
Flux または Redux の考え方は主に関数型プログラミング(FP)の考え方であるため、学ぶのが少し大変に感じるかもしれない。一方、MobX はオブジェクト指向プログラミングに近く、状態を観察可能なオブジェクトとしてラップし、このオブジェクトがさまざまな変更を駆動する。観察可能とは何か?それは MobX の「お兄さん」が状態を見守っているということだ。状態が変わると、それを使用しているすべての場所が変わる。こうして View 全体が状態によって駆動される。
const obj = observable({
a: 1,
b: 2
})
autoRun(() => {
console.log(obj.a)
})
obj.b = 3 // 何も起こらない
obj.a = 2 // observe関数のコールバックがトリガーされ、コンソールに出力:2
上記の obj の obj.a 属性が使用されているため、obj.a 属性が変わると、すべての使用場所が呼び出される。autoRun はこの「お兄さん」で、obj.a に依存するすべての場所を収集する。obj.a が変わると、「お兄さん」はすべての依存関係をトリガーして更新する。
MobX は Flux や Redux と同様に、具体的なフロントエンドフレームワークに依存しない。つまり、React(mobx-react)や Vue(mobx-vue)で使用できる。一般的には、React で使用されることが多く、Vue ではあまり使用されない。なぜなら、Vuex 自体が MobX に似ていて非常に柔軟だからである。MobX を React や Vue で使用すると、setState () や this.state.xxx のような処理を省略できることが多い。