コアのコンセプト
アプリケーションの状態がプレーンオブジェクトとして記述されていると想像してみてください。たとえば、ToDo アプリの状態は次のように見える可能性があります
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
このオブジェクトは「モデル」に似ていますが、セッターがない点が異なります。これは、コードのさまざまな部分が状態を勝手に変更して再現性の低いバグを引き起こすことがないようにするためです。
状態内の何かを変更するには、アクションをディスパッチする必要があります。アクションはプレーンな JavaScript オブジェクト(魔法のような要素が一切ないことに注目)で、何が起こったのかを記述しています。以下にいくつかのアクションの例を示します
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
すべての変更がアクションとして記述されていることを強制することで、アプリで何が起こっているのかを明確に理解できます。何かが変わったら、その理由がわかります。アクションは、発生した事象のパンくずリストのようなものです。最後に、状態とアクションを関連付けるために、reducer と呼ばれる関数を記述します。繰り返しますが、魔法のようなものではありません。状態とアクションを引数として取り、アプリの次の状態を返す関数です。大規模なアプリケーションでそのような関数を記述するのは難しいので、状態の一部を管理する小さな関数を記述します
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter
} else {
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
さらに、対応する状態のキーに対するこれら 2 つの reducer を呼び出すことで、アプリの完全な状態を管理する別の reducer を記述します
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
これが Redux の全体的なアイデアです。Redux API を使用していないことに注目してください。このパターンを容易にするために少しユーティリティが付属していますが、主な考え方は、アクションオブジェクトに応じて時間の経過とともに状態がどのように更新されるかを記述することで、記述するコードの 90% が Redux 自体、その API、またはあらゆる魔法を使用しないで、プレーンな JavaScript であるということです。