Redux の利点を振り返る

Redux の利点を振り返る

readyfor_redux_study#1

READYFOR株式会社 さんで開催した、Redux 勉強会第一回の資料です。

5d9cd19df0e91caac118b793b4f803d5?s=128

Takepepe

June 29, 2020
Tweet

Transcript

  1. 2.

    Redux の利点 TL;DR ◼ イベント駆動 ◼ React と 疎結合 ◼

    エコシステムが豊富 ◼ 開発者母数が多い
  2. 8.

    Action って何? ◼ Action は、ActionType と Payload が「対」になった Object ◼

    ActionType は、prj でユニークな識別子(文字列) ◼ Payload は、任意の値Object ◼ ActionCreator は「対」を生成するインターフェース const action = { type: "COUNTER_ADD", // prj で重複しない識別子 payload: { amount: 10 } // 任意の値 }
  3. 9.

    Action って何? ◼ Action は、ActionType と Payload が「対」になった Object ◼

    ActionType は、prj でユニークな識別子(文字列) ◼ Payload は、任意の値Object ◼ ActionCreator は「対」を生成するインターフェース const action = { type: "COUNTER_ADD", // prj で重複しない識別子 payload: { amount: 10 } // 任意の値 }
  4. 10.

    Action って何? ◼ Action は、ActionType と Payload が「対」になった Object ◼

    ActionType は、prj でユニークな識別子(文字列) ◼ Payload は、任意の値Object ◼ ActionCreator は「対」を生成するインターフェース const action = { type: "COUNTER_ADD", // prj で重複しない識別子 payload: { amount: 10 } // 任意の値 }
  5. 11.

    Action って何? ◼ Action は、ActionType と Payload が「対」になった Object ◼

    ActionType は、prj でユニークな識別子(文字列) ◼ Payload は、任意の値Object ◼ ActionCreator は「対」を生成するインターフェース cconst counterAdd = (amount) => ({ type: "COUNTER_ADD", // prj で重複しない識別子 payload: { amount } // 任意の値 })
  6. 12.

    State はどこにある? Reducer と呼ばれる関数が、状態(State)を抱えています。 const reducer = (state = {

    count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  7. 13.

    State はどこにある? Reducer と呼ばれる関数が、状態(State)を抱えています。 const reducer = (state = {

    count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  8. 14.

    State はどこにある? 状態(State)を変更できるのは、Reducer だけです。 const reducer = (state = {

    count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  9. 15.

    Reducer 処理の流れ dispatch(action) の度に、Reducer の処理は実行されます。 const reducer = (state =

    { count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  10. 16.

    Reducer 処理の流れ まずは Action を受け取ります。 const reducer = (state =

    { count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  11. 17.

    Reducer 処理の流れ Action は、様々なものが降ってきます。 const reducer = (state = {

    count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  12. 18.

    Reducer 処理の流れ ActionType で、興味のある Action を選り分けます。 const reducer = (state

    = { count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  13. 19.

    Reducer 処理の流れ 興味のある Action が発生した場合、値を更新した新しい state を返します。 const reducer =

    (state = { count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  14. 20.

    興味のない Action が発生した場合、そのまま state を返却します。 const reducer = (state =

    { count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } } Reducer 処理の流れ
  15. 21.

    Reducer 処理の流れ 返却された state は、次回 Action 発生時の第一引数になります。 const reducer =

    (state = { count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  16. 22.

    Reducer 処理の流れ これが Reducer 処理の一連です。Action を選り分け、必ず state を返却します。 const reducer

    = (state = { count: 0 }, action) => { switch (action.type) { case "COUNTER_ADD": const count = state.count + action.payload.amuont return { ...state, count } default: return state } }
  17. 24.

    React.useReducer との違い React の標準 API に、useReducer がありますが、 そこで利用する Reducer 関数も、

    Flux 実装であり処理の流れは同じです。 では、Redux との違いは何でしょうか?
  18. 25.

    React.useReducer との違い React.useReducer の場合、特定の State に向けて、 特定の Action が発行される事を想定し、定義します。 const

    reducer = (state = { count: 0 }, action) => { switch (action.type) { case "INCREMENT": return { ...state, count: state.count + 1 } case "DECREMENT": return { ...state, count: state.count - 1 } default: return state } }
  19. 26.

    React.useReducer との違い 一方で、Redux の場合、特定の State に向けて、 特定の Action が発行されるとは限りません。 const

    reducerA = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return { ...state, count: state.count + 1 } case "B:INCREMENT": return state default: return state } } const reducerB = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return state case "B:INCREMENT": return { ...state, count: state.count + 1 } default: return state } }
  20. 27.

    React.useReducer との違い Redux の Reducer は、複数の Reducer を 「一つの Reducer

    として連結」することが出来るのです。 const reducerA = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return { ...state, count: state.count + 1 } case "B:INCREMENT": return state default: return state } } const reducerB = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return state case "B:INCREMENT": return { ...state, count: state.count + 1 } default: return state } }
  21. 28.

    React.useReducer との違い reducerA に向けて、意識的に発行した Action であっても、 reducerB に勝手に到達します。(逆も然り) const reducerA

    = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return { ...state, count: state.count + 1 } case "B:INCREMENT": return state default: return state } } const reducerB = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return state case "B:INCREMENT": return { ...state, count: state.count + 1 } default: return state } }
  22. 29.

    React.useReducer との違い Redux の場合 Action / Reducer 共に、Global な文脈に属します。 「ActionType

    が一意な識別子」でなければいけないのは、このためです。 const reducerA = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return { ...state, count: state.count + 1 } case "B:INCREMENT": return state default: return state } } const reducerB = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return state case "B:INCREMENT": return { ...state, count: state.count + 1 } default: return state } }
  23. 30.

    Redux Toolkit の Slice を利用すると、この挙動に気づきにくくなっています。 この挙動を利用する設計パターンが、Redux の強みとも言えます。 Redux Toolkit /

    createSlice に注意 const reducerA = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return { ...state, count: state.count + 1 } case "B:INCREMENT": return state default: return state } } const reducerB = (state = { count: 0 }, action) => { switch (action.type) { case "A:INCREMENT": return state case "B:INCREMENT": return { ...state, count: state.count + 1 } default: return state } }
  24. 32.

    Global な文脈の何が嬉しいのか? Redux の場合「Action と Reducer 」は設計上疎結合です。 様々な Action が

    Global な文脈で発生し続けるので、 各々の Reducer は興味のあるものだけを購読し、 各々のユースケースに応じて、利用します。
  25. 36.

    Global な文脈の何が嬉しいのか? 開閉状態を変更できるのは、”UI_HEADER::TOGGLE” という Action が発生した時です。この機能は無事、リリースを迎えました。 UIHeader Reducer const UIHeaderReducer

    = (state = { open: false }, action) => { switch (action.type) { case "UI_HEADER::TOGGLE": return { ...state, open: action.payload.open } default: return state } }
  26. 41.

    Global な文脈の何が嬉しいのか? Global な文脈がここで活きます。”UI_MODAL::TOGGLE” が発生したら、 UIHeader Reducer は必ず「閉じる」状態にすれば良いだけです。 UIModal Reducer

    UIHeader Reducer const UIHeaderReducer = (state = { open: false }, action) => { switch (action.type) { case "UI_HEADER::TOGGLE": return { ...state, open: action.payload.open } case "UI_MODAL::TOGGLE": if (!state.open) return state return { ...state, open: false } default: return state } }
  27. 44.

    Global な文脈の何が嬉しいのか? これも簡単ですね。”ROUTER::LOCATION_CHANGE” が発生したら、 各々の UIReducer が「閉じる」状態に変更すれば良いのです。 UIHeader Reducer Router

    Reducer UIModal Reducer const UIHeaderReducer = (state = { open: false }, action) => { switch (action.type) { case "UI_HEADER::TOGGLE": return { ...state, open: action.payload.open } case "UI_MODAL::TOGGLE": if (!state.open) return state return { ...state, open: false } case "ROUTER::LOCATION_CHANGE": if (!state.open) return state return { ...state, open: false } default: return state } }
  28. 51.

    Redux Toolkit / createSlice に注意 アクションは、単一のスライスに限定されません。 レデューサーロジックのどの部分も、 ディスパッチされたアクションに応答できます(そして、そうすべきです!)。 Actions are

    not exclusively limited to a single slice. Any part of the reducer logic can (and should!) respond to any dispatched action. createSlice では、この設計パターンに気づきにくくなっているため、 Reuducer 本来の挙動を忘れない様にしましょう(以下ドキュメント引用)
  29. 52.

    Redux Toolkit / createSlice に注意 実現するためには、extraReducers を利用する必要があります。 extra というニュアンスですが、本来の挙動なので積極的に活用しましょう。 アクションは、単一のスライスに限定されません。

    レデューサーロジックのどの部分も、 ディスパッチされたアクションに応答できます(そして、そうすべきです!)。 Actions are not exclusively limited to a single slice. Any part of the reducer logic can (and should!) respond to any dispatched action.