[Redux] Redux Basic 基礎
安裝
# 搭配 CRA 的安裝方式
$ npx create-react-app my-app --template redux
觀念
- 當我們搭配 Redux 使用之後,所有的資料處理(包含 AJAX)都是透過 Redux 來進行,React 只負責資料的呈現。
- 你可能會經常看到 state 和 store 這兩個字交互著使用,精確來說 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 就是單純帶有 type
和 payload
的 物件(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 會接收
previousState
和action
作為參數,來回傳新的 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
- 透過
store.dispatch(action)
發送 action , action 是透過 action creator 產生的物件。 - 發送 action 後,Redux 的 store 會呼叫代入
createStore()
裡面的 reducers 函式,並傳入當前的資料狀態(previousState)和動作(action),每個 reducers 函式便可以取用到previousState
和action
,並回傳新的 state。 - Redux 的 store 會掌握所有透過 reducers 回傳而來的新 state,並重新整合成單一的 state。
- 所有透過
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;
}
}
參考
- Flux with React @ Sandbox Editor
- :thumbsup: (譯)圖解 Flux - A cartoon guide to Flux
- :thumbsup: (譯)圖解 Redux - A cartoon Intro to Redux