跳至主要内容

[Redux] Redux Basic 基礎

安裝

# 搭配 CRA 的安裝方式
$ npx create-react-app my-app --template redux

觀念

  • 當我們搭配 Redux 使用之後,所有的資料處理(包含 AJAX)都是透過 Redux 來進行,React 只負責資料的呈現。
  • 你可能會經常看到 statestore 這兩個字交互著使用,精確來說 state 指的是資料本身,而 store 指的是資料存放的地方。
  • 在搭配 redux 使用 react 時會載入兩個不同的函式庫:
    • redux:存放資料狀態的地方,當資料狀態改變的時候會有反應。
    • react-redux:讓 redux 中的狀態可以和 React 相連接。
  • Higher Order Function(HOF):又稱作 higher-order component(HOC),在 Redux 中,會看到 connect(mapStateToProps)(ComponentName) 這種用法。這裡個 connect 就是 higher-order function,簡單來說就是指當你呼叫它時,它會回傳一個函式,接著把這個函式再代入 Component 作為參數後,它會包一個新的 Component 出來。

動機(motivation)

  • 透過 Redux 希望能夠讓資料的變更(state mutations)是可預期的。
  • 透過 Redux 提供的工具,更容易了解 App 中資料狀態變更的 when, where, why, how。
  • 讓寫出來的程式碼是容易被預測與測試的。

核心概念(core concepts)

在 Redux 中有三個主要的東西:

  • 資料(state):一個物件,包含所有的資料狀態。
  • 動作(action):一個物件,用來描述對資料要進行什麼樣的操作。
  • reducer:一個函式,根 action 的描述對原有的資料進行變更。

三個原則(three principles)

  • Single source of truth:整個 App 的資料狀態(state)都保存在單一的物件中,這個保存所有資料的物件稱作 store。
  • State is read-only:要改變資料狀態的唯一方式就是透過 action。
  • Change are made with pure functions:reducers 是一個單純的函式,它會根據 action 和原有的 state,再不變更原有 state 的情況下回傳新的 state。

基本元件

Action、Action Creators 和 Bound Action Creator

Action

Action 就是單純帶有 typepayload物件(object),用來說明想要對資料做哪些改變:

// action
{
type: 'ACTION_TYPE',
payload: 'some data'
}

Action Creator

Action Creator 則是用來產生 Action 的 「函式(function)」

// action creator
const fetchData = (id) => ({
type: 'FETCH_DATA',
payload: id,
});
  • store.dispatch(action) 後,該 action 會自動派送到所有的 reducer
  • 根據 Flux Standard Action(FSA) 的規範,每個 action 一定是個物件,且要包含 type 屬性,其他選擇性的屬性包含 error, payload, meta,任何其他名稱的屬性都是不被允許的。

Bound Action Creator

當要發送 action 讓 reducer 收到時,需要透過 dispatch 這個方法,像是這樣:

// dispatch 一個 action
dispatch(fetchData(3));

由於每次都要寫 dispatch(actionCreator()),因此常見的會把 dispatch 和 action creator 綁在一起,稱作 Bound Action Creator,像是這樣:

// bound action creator
const boundFetchData = (id) => dispatch(fetchData(id));

// 這時候要發送 action 時,只需要直接呼叫
boundFetchData(3);

redux 的套件中也有提供 bindActionCreators() 的方法可供使用。

Reducers:改變和更新 state

Reducer 是一個「函式(function)」,目的是用來改變和更新 state,它會根據 action 和原本的 state 回傳新 state

// reducer
const posts = (state = [], action) => {
switch (action.type) {
case 'FETCH_POSTS':
return action.payload.data;
}
return state;
};
  • 是一個 pure function 會接收 previousStateaction 作為參數,來回傳新的 state
  • reducer 一定要是 pure function,也就是要回傳新的 state(immutable),而不是去改變原本的 state(mutable);也不能呼叫非 pure function,例如 Date.now(), Math.random()不要在裡面呼叫 API,這些不允許的行為都應該在 store.dispatch(action) 之前處理。
  • 所有的 reducer 都會接收到 action,並回傳 state,這個資料狀態有可能是新的或舊的。
  • Container 是需要和 Redux 溝通的元件。技術上來說,container 只是使用了 store.subscribe() 的 React 元件;但實際上建議還是使用 Redux 提供的 connect 方法,它幫我們避免掉更多可能減弱效能的問題。

⚠️ reducers 一定要是 pure function,也就是說,當給予相同的參數時,處理後會得到相同的結果。在這裡面不能有任何副作用、不要呼叫 API,就是單純的運算。

Store

  • 會擁有整個 App 的 state,在 Redux 中總是只會有一個 store,但可以有多可 reducer
  • state 有更新時,store 會通知有訂閱的 container 有新的 state。
  • 透過 getState() 可以取得 state 的資料。
  • 透過 dispatch(action) 可以更新 state 的資料。
  • 透過 subscribe(listener) 可以註冊監聽事件。

流程(data flow)

在 Redux 中所有資料狀態的改變流程都一樣:Action Creator ->Dispatcher -> Reducer -> Store -> new state

  1. 透過 store.dispatch(action) 發送 action , action 是透過 action creator 產生的物件。
  2. 發送 action 後,Redux 的 store 會呼叫代入 createStore() 裡面的 reducers 函式,並傳入當前的資料狀態(previousState)和動作(action),每個 reducers 函式便可以取用到 previousStateaction,並回傳新的 state。
  3. Redux 的 store 會掌握所有透過 reducers 回傳而來的新 state,並重新整合成單一的 state。
  4. 所有透過 store.subscribe(listener) 註冊的 listener 機會被執行,並且可以透過 store.getState() 取得最新的資料狀態。

Action Snippets

Basic Flux Standard Action
{
type: 'ADD_TODO',
payload: {
text: 'Do something.'
}
}
FSA which represents an error
{
type: 'ADD_TODO',
payload: new Error(),
error: true
}
index property in Redux
{
type: TOGGLE_TODO,
index: 5
}
filter property in Redux
{
type: SET_VISIBILITY_FILTER,
filter: SHOW_COMPLETED
}

Reducer Snippets

Redux 在第一次呼叫 reducers 時,state 會是 undefined,因此可以在這裡定義起始資料狀態:

import { VisibilityFilters } from './actions';

const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: [],
};

function todoApp(state = initialState, action) {
// For now, don't handle any actions
// and just return the state given to us.
return state;
}

在 reducer 中一定要用 immutable 的方法,所以使用 Object.assign() 來回傳一個新物件。另外,對於未知的 action,記得要回傳原本的 state 資料狀態:

function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
// 不要 mutate 原本的 state
// 等同於 {...state, {visibilityFilter: action.filter}}
return Object.assign({}, state, {
visibilityFilter: action.filter,
});

// 對於未知的 action 要回傳原本的 state
default:
return state;
}
}

參考