💭

Redux 的基本概念

2023/08/28に公開

前言與 Redux 介紹

文章內容為 codecademy 的學習筆記。

在 React 中,每個部分都是一個 component ,而資料需要透過這些 component 來分享與更新。開發者們稱這些資料為這個應用程式的 State,同時這個分享與更新的過程稱作 state management。但隨著愈來愈大且複雜的 State,僅僅靠著純粹的 React 可能會難以管理。

這時候就需要 Redux,Redux 是一個 state management library,他遵從 Flux architectural pattern。在 Flux pattern 中,這些分享中的資訊不是存在 component 中,而是在 single object。Component 只是提供要渲染的資料,並可以使用 actions 來請求更改。 State 在整個應用程式中都是可用的,並以可預測的方式進行更新:每當對 State 進行更改時,都會“通知” component。

這裡引用 Redux 文件的描述來做個統整:“The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur. Redux guides you towards writing code that is predictable and testable, which helps give you confidence that your application will work as expected.”

One-Way Data Flow

在大多數的應用程式中,可以分為以下三個部分。

  • State – the current data used in the app
  • View – the user interface displayed to users
  • Actions – events that a user can take to change the state

而資料流的方式可以像是:

  1. State 會儲存 component 當下的資料。
  2. View 會顯示 component 當下的資料。
  3. 當使用者與 view 互動的時候,可能像是點擊按鈕之類的,state 將會以某種方式來更新。
  4. View 也會更新新的 state。

若只以純粹的 React 來執行以上的流程,同時間會需要做很多事,不僅要渲染畫面,也要管理自己的 state,且當 action 改變 state 時,component 也需要彼此溝通這些變動,會讓以上這三種動作變得相當重疊且複雜。

而 Redux 會藉由要求單一來源管理的 state 來協助分離這三個動作。更改 state 的請求以 action 的形式透過 component 傳送到某個單一來源。受這些變化影響的任何 component 都由這個單一來源來告知。而透過強加這種結構,Redux 讓程式更具可讀性、可靠性和可維護性。

State

State 它代表的是應用程式中當前的資料。

舉例:如果是一個月曆 App,他可能包含名稱、日期與標籤。如果是一個 to-do App,他可能含有描述、完成與未完成的資訊。

State 可以是任何 Javascript 的型別,數字、字串、布林、陣列或是物件。比如以下的例子:

const state = [ 'Print trail map', 'Pack snacks', 'Summit the mountain' ];

每一個 State 都會對應某部分的使用者介面。

State 具有以下特點:

  1. 單一來源:Redux 的 state 存儲在一個稱為 "store" 的單一來源中,這有助於確保 state 的一致性和可追蹤性。
  2. 只讀:state 是唯讀的,這意味著不能直接修改state。要改變 state,必須用發送一個 action 的方式。
  3. 通過純函數修改:要改變狀態,需要創建一個純函數稱 reducer,它接受先前的 state 和 action 作為輸入,並返回一個新的 state。這個動作保證了state 的可預測性和可追蹤性。

Action

Action 是描述發生在應用程式中事件的JavaScript object,用於描述應用程式中的某種操作,例如使用者點擊按鈕、資料從伺服器請求等。

每個動作都擁有兩個主要屬性:

  1. type:這是一個字串,用於指定動作的類型。它是表示動作的唯一標籤,Redux 會根據此來確定如何處理該動作。例如:{ type: 'INCREMENT' }
  2. payload:這是與動作相關的屬性。它可以是任何 JavaScript 的型別,如字串、數字、物件等,取決於動作的具體需求。例如:{ type: 'SET_USER', payload: { id: 1, name: 'John' } }

比如以下例子:

const action = {
  type: 'todos/addTodo',
  payload: 'Take selfies'
};

當一個 action 產生,且已經通知到應用程式中其他部分的話,會說這個 action 已經被發送(dispatched) 出去了。

Reducers

它定義了 the current state 和 action 會如何組合,以建立新的 State,也就是負責處理應用程式 state 更新的函數。它接受兩個參數:the current state 以及 action,然後根據 action 的 type 來計算並返回一個新的 state。

以下用一個 todo app 的例子來說明:

const initialState = [ 'Print trail map', 'Pack snacks', 'Summit the mountain' ];
 
const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'todos/addTodo': {
      return [ ...state, action.payload];
    }
    case 'todos/removeAll': {
      return [];
    }
    default: {
      return state;
    }
  }
}
  • 它是一個 JavaScript function 。
  • 它透過給予的 the current state 跟 action 來定義應用程式的下一個 state。
  • 若 action 沒有提供,會回傳一個預設的 initialState
  • 若 action 沒有被確認,會回傳 the current state。

在新增資料的時候,常用 the spread operator (...) 的方式來拷貝 the current state 以及任何更動到新的物件中,而不是直接對 the current state 做操作。

關於 reducers 有以下規則:

  1. 它只接受 state 以及 action 來計算出新的 state。
  2. The current state 不能被修改,只能透過拷貝再加上要改變的部分。
  3. 它不允許任何非同步的方式,或是其他 side effects。

以上的規則讓 Redux 的程式碼變得可預測、可靠,且容易去 debug。

Immutable Updates and Pure Functions

Reducer 一定要是 immutable updates 以及 pure functions。若一個 function 要對參數做 immutable updates,要做的不是直接改變參數,而是拷貝之後再對參數做改變。

Immutable Updates

以下舉個例子:

  • 一般改變的方式:
const mutableUpdater = (obj) => {
  obj.completed = !obj.completed;
  return obj;
}
 
const task = { text: 'do dishes', completed: false };
const updatedTask = mutableUpdater(task);
console.log(updatedTask); 
// Prints { text: 'do dishes', completed: true };
 
console.log(task); 
// Prints { text: 'do dishes', completed: true };
  • immutable updates 改變的方式,它保留原本的資料,對它拷貝後再做改變。
const immutableUpdater = (obj) => {
  return {
    ...obj,
    completed: !obj.completed
  }
}
 
const task = { text: 'iron clothes', completed: false };
const updatedTask = immutableUpdater(task);
console.log(updatedTask); 
// Prints { text: 'iron clothes', completed: true };
 
console.log(task); 
// Prints { text: 'iron clothes', completed: false };

Pure Functions

若一個 function 是 pure functions,也就代表說,給予它相同的 input 就理當要有相同的 output。

以下舉一個例子:

  • 這個 function 不是一個 pure function,因為他的回傳值依賴遠端的狀態。
const addItemToList = (list) => {
  let item;
  fetch('https://anything.com/endpoint')
    .then(response => {
      if (!response.ok) {
        item = {};
      }
      
      item = response.json();
   });
 
   return [...list, item];  
};
  • 這個 function 可以是個 pure function,藉由把 fetch() 拉到 function 外使用,避免對遠端狀態的依賴。
let item;
  fetch('https://anything.com/endpoint')
    .then(response => {
      if (!response.ok) {
        item = {};
      }
      
      item = response.json();
   });
 
const addItemToList = (list, item) => {
    return [...list, item];
};

Store

目前為止,已經說明了 state, actions 以及 reducers。但他們如何參與 Redux 的資料流?以及它們會在哪裡發生?

Redux 使用一個稱為 store 的 object。 Store 作為 state 的容器,它提供發送 action 的方法,且會在發送 action 時呼叫 reducer。

所以把 store 加入資料流的話會是以下這樣:

  1. Store 會用一個預設值來初始化 state。
  2. View 會顯示 the current state。
  3. 當使用者與 view 互動的時候,像是點擊按鈕之類的,action 會發送到 store。
  4. 被發送的 action 以及 the current state 會合併進 store 的 reducer 中,來決定下一個 state。
  5. View 會被更新來顯示新的 state。

文章參考

Learn Redux

React + GraphQL 全端練習筆記 - Redux 簡介 by Jasper

Discussion