Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Redux の利点を振り返る

Redux の利点を振り返る

readyfor_redux_study#1

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

Takepepe

June 29, 2020
Tweet

More Decks by Takepepe

Other Decks in Technology

Transcript

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

    エコシステムが豊富 ◼ 開発者母数が多い
  2. Action って何? ◼ Action は、ActionType と Payload が「対」になった Object ◼

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

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

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

    ActionType は、prj でユニークな識別子(文字列) ◼ Payload は、任意の値Object ◼ ActionCreator は「対」を生成するインターフェース cconst counterAdd = (amount) => ({ type: "COUNTER_ADD", // prj で重複しない識別子 payload: { amount } // 任意の値 })
  6. 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. 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. 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. 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. 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. 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. 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. 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. 興味のない 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. 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. 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. React.useReducer との違い React の標準 API に、useReducer がありますが、 そこで利用する Reducer 関数も、

    Flux 実装であり処理の流れは同じです。 では、Redux との違いは何でしょうか?
  18. 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. 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. 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. 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. 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. 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. Global な文脈の何が嬉しいのか? Redux の場合「Action と Reducer 」は設計上疎結合です。 様々な Action が

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