iT邦幫忙

2022 iThome 鐵人賽

DAY 1
4
自我挑戰組

30天深入淺出Redux系列 第 1

Redux 深入淺出 - [ Day 1 ] Redux 簡介

  • 分享至 

  • xImage
  •  

前導

這次的主題是板上已有非常多資源的Redux,但時代在變,Redux也在變,有很多做法已經不再適合現在主流 React functional component 的做法了,所以,本系列會以簡單介紹 Redux,包含基本概念與實作,還有官方推薦的Redux toolkit 的實作為主,內容取自於自己在工作上使用的經驗與網路上 youtube 的影片教學的內容集合濃縮,會偏向實際使用層面為主,在這系列文章當中你會實作 Redux 原生的 app,透過導入如何應用 Redux toolkit,和 React-redux 與 React 專案的結合。

關於這個系列的章節

  • Day1 ~ Day11: 為純 Redux 的做法,這部分可以適用至早期的環境應用,方便大家了解原生的結構。
  • Day12 ~ Day17: 為 Redux 搭配 Redux-toolkit 的做法,比較偏向現代線的用法,一樣是單純環境可以搭配不同框架應用。
  • Day18 ~ Day24: 為結合 React、Redux、Redux-toolkit、React Redux 的作法,建構環境為 js,方便 React 初學者參考應用。
  • Day25 ~ Day30: 為 RTK Query 的相關應用,這部分為 Redux 官方借鏡 React Query 所延伸出來的解決方案,那這部分的環境就是採 Typescript,屬進階一點的作法,但也很適合初學應用。

什麼是 Redux?

Redux 本身是作為一個 Javascript 的狀態管理工具,所以不是一定只能和 React 做合體,一樣可以在各種不同框架下應用,在這個系列文章裡我將會示範純 Redux 是如何來做到 share 這些不同暫存的資料的。

他的概念和 React 本身的 useReducer hook 的概念很像,如果大家已經會用的話應該很快能夠映射這樣的管理工具,因為 useReducer hook 也是借鏡 Redux 而發展出來的 hook function,再透過 context provider 的原理做成 global state 以供專案內不同 components 互相引入使用。

如此你就能夠將畫面上的資料與UI的部分分離開來,以便後續的維護。

為什麼需要 Redux?

在大多數 SPA 架構下的專案中,一定會有需要做到 global state 的需求,那透過 redux 就能更有效地去管理所有的 global state,以 react 本身為例,你會需要透過 createContext 來定義要分享出來的 state 結構,再透過 provider 來決定分享出來的對象,但是數量一多,這部分的管理就需要優化,那 Redux 就能解決你的需要。Redux 提供的模式和工具更容易理解專案中的狀態何時、何地、為什麼以及如何更新,以及當這些更改發生時您的應用程序邏輯將如何表現。

使用時機

  • 專案中大量的 state 需要在很多不同的地方使用
  • 你的 state 會很頻繁的更新
  • state 更新的邏輯很複雜
  • codebase 較大,而且需要很多人共同開發

Redux 的基本觀念

讓我們回到 react 的 component 來解釋 Redux 是怎麼定義 Action的。

function Counter() {
  // State: useState的基本,定義出state
  const [counter, setCounter] = useState(0)

  // Action: 這就是我們一般常見定義的state change function 
  const increment = () => {
    setCounter(prevCounter => prevCounter + 1)
  }

  // View:
  return (
    <div>
      Value: {counter} <button onClick={increment}>Increment</button>
    </div>
  )
}

透過這個圖表應該更能夠清楚解釋上面 react code 的邏輯
https://ithelp.ithome.com.tw/upload/images/20220902/20129020CKVrCwGi19.png

這樣簡單的模組看似上沒有什麼問題,但當你專案的 state 開始變多又要分享給其他的 component 去使用的時候,這樣的結構就無法負荷。解決這個問題的一種方法是從 component 中提取共享的 state,並將其放在 component 組合之外的集中位置 (ex: context provider + useReducer hook)。如此,我們的 component 組合就會只剩下 View 的部分,任何 component 都可以訪問 state 或觸發 Action,簡單來說就是將 State & Action 的部分給拆出來,透過引入的方式去處理 State,這樣能更有效的利用每個 state。

Redux 常會用到以下幾個關鍵字:

  • Actions:
    這裡的做法有點像是指向,通常的會是回傳一個 javascript 物件,這個物件裡會有對應的 type & payload,以下為範例:
const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}
// 或是採用function
const addTodo = text => {
  return {
    type: 'todos/todoAdded',
    payload: text
  }
}
  • Reducers:
    這裡的作法基本上就和官方的 useReducer hook 是一樣的,可以依據不同的 action type 去處理不同的 state change function。

    此外有幾個原則必須注意:

    • 只會根據傳入的state & action 來處理新的state。
    • 不允許修改現有state,相反的必須通過複製現有狀態並對複制的值進行更改來進行更新。
    • 避免在此處理async function,會造成其他”side Effect”。
// sample from redux office doc
const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // 檢查核對actio.type的state change function
  if (action.type === 'counter/increment') {
    // 如果有, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}
  • Store:
    在使用 Redux 時 state 會存在於一個名為 store 的對像中,透過 createStore 或是 configureStore 來創建 store,store 是通過傳入一個 reducer 創建的,並且有一個名為 getState 的方法,它返回當前狀態值。
// sample code from redux doc
import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}
  • Dispatch:
    Redux store 有一個也是唯一一個更新 state 的方法 store.dispatch() ,並且傳入 action object 做為參數,接著他會去觸發 reducer 內所定義好的 function 並更新 state。
// sample code from redux doc
store.dispatch({ type: 'counter/increment' })

console.log(store.getState())
// {value: 1}
  • Selectors:
    selector
    是知道如何從 store 中提取特定 state 信息的 function。 隨著應用程序變得越來越大,這有助於避免重複邏輯,因為應用程序的不同部分需要讀取相同的數據:
// sample code from redux doc
const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

以下為 Redux data Flow 的示意圖,這個圖片出處為 redux 官方文件內的動畫,已非常清楚的解釋了上述各個關鍵字,在整個資料流裡面分別扮演的角色。

https://redux.js.org/assets/images/ReduxDataFlowDiagram-49fa8c3968371d9ef6f2a1486bd40a26.gif

那麼今天的內容就先到這裡,明天我們來實際使用 redux 做一個新的專案吧!
參考文獻:
Redux官方


下一篇
Redux 深入淺出 - [ Day 2 ] Project setup
系列文
30天深入淺出Redux31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
diu7me
iT邦新手 4 級 ‧ 2022-09-30 17:03:35

寫得很細心, 不錯不錯,加油

LucianoLee iT邦研究生 5 級 ‧ 2022-10-11 16:50:41 檢舉

謝謝,也歡迎直接到我 gitHub連結 查看細節

我要留言

立即登入留言