GUIプログラミングにおけるActionとReducerの関係性をコードで示す
関連
https://scrapbox.io/files/649e134de9fa1b001bb42f1c.png
毎回このパターンに落とし込むかは対象の性質に依る
koushisa.iconの経験則と勘所
---
Action
外部から引数を受け取る
状態遷移の元となる過不足のないオブジェクトを作成する
このとき、非同期処理を含めてもよい
エラーハンドリングもここでやる
ステートの更新をAction内でやるか、Reducerでやるかはその都度検討する Actionで更新するほうが全体の流れを俯瞰できるし実装も楽な気はする
ReduxだとReducerでやるけど、ポイントは状態遷移を純粋関数で切り出すこと code:typescript
type Action = {
type: "POST_NAME_UPDATED",
payload: {
id: number,
name: string
},
type: "POST_REMOVED",
payload: {
id: number
},
type: "POSTS_DESTOYED",
}
// ステートの事前条件、不変条件
const schema = /*~*/
// ここから下がAction
const updatePostName = (postId: number, name: string) => {
// 事前準備
// 非同期処理やらDomain Objectつくるやらなんやら
const payload = schema.parse({id:postId, name})
// reducerへ新しいステートへの状態遷移を閉じ込める
const newPosts = reducer({
type: POST_NAME_UPDATED,
payload
})
// ステートを更新する
posts.setState(newPosts)
}
// 削除も同様
const removePost =(postId:number) => {
posts.setState(reducer({type: "POST_REMOVED"}))
}
Reducer
関数の中でsetStateする副作用で変化させるのではなく、漸化式で新しいステートを返すのがポイント 新しいステート = オブジェクトの実体が変わる
Reactの場合はObject.isによってステートが変化したことを検知できる code:typescript
const reducer = (action: Action) => {
switch(action.type){
case: "POST_NAME_UPDATED":
return /* payloadとstoreから新しいposts */
case: "POST_REMOVED":
return /* storeからpayloadを削除した新しいposts */
case: "POSTS_DESTOYED":
return /* storeの初期値 */
}
}
利用側
中で何が起こるかの詳細は知らない
code:typescript
updatePostName(1, "foo")
removePost(1)
これをしちゃうと事前条件違反がおきたりする
責任の所在がわからずデバッグが困難になる