總結
- 要求
Action
return
一個接受dispatch
的function
,而不是一個單純的Plain Object。 - 可將各種狀態改變與資料操作的邏輯放在
Action
中
前言
前接
先說說我認為的Redux運作方式
Component
建立Action
。Component
呼叫Store
的dispatch,並送入Action
當參數。Store
把Action
傳給各個Reducer
,看哪一個Reducer
要處理。Reducer
處理完後產生新的State
,再交回給Store
Store
更新本身保留的State
後,再去更新相關的Componet
中的State
。
看來Component
不是很忙,但事實上,Component
會很忙,因為資料操作的邏輯也要放在它身上。
實例
一樣用Todo List
當例子,主要顯示的Component要做幾件事,先使用圖示或文字在畫面上提示Loading並將系統變為載入中的狀態,然後透過fetch
取得todo list後顯示資料,再通知系統變為載入完成的狀態,以便去除載入圖示或文字。
寫成程式大概是這樣
一、最開始什麼都不用
componentDidMount() {
this.setState({loading:true});
fetch(API)
.then(response => response.json())
.then(data => {
this.setState({loading:false, todos:data});
}).catch(data => {
this.setState({loading:false});
});
}
二、改用Redux
componentDidMount() {
this.props.showLoading();
fetch(API)
.then(response => response.json())
.then(todos => {this.props.listTodos(todos)})
.then(()=>{this.props.finishLoading()})
.catch(()=>{this.props.finishLoading()});
}
/** 跟Redux說明哪些State是它要關心的 */
function mapStateToProps(state) {
return {
loading : state.todoState.loading,
todos : state.todoState.todos
};
}
/** 跟Redux拿一下dispatch,把會傳送的Action工作先標起來 */
function mapDispatchToProps(dispatch) {
return {
showLoading : () => {
dispatch(todoActions.showLoading());
},
listTodos : (todos) => {
dispatch(todoActions.listTodos(todos));
},
finishLoading : () => {
dispatch(todoActions.finishLoading());
}
};
}
三、再把fetch
移出去到一個Service
來集中管理
asyncLoad = async () => {
this.props.showLoading();
const todos = await todoService.pureFetchAllTodos();
this.props.listTodos(todos);
this.props.finishLoading();
}
稍唯想一下Component
要做的工作
- 跟
Redux
說明哪些State
是它要關心的 - 因為不想直接操作
Store
,就只好先把要做的dispatch
工作告訴Redux
- 操作
dispatch
來改變狀態 - 使用
Service
來操作資料
其中的1、2項比較無法避免(其實也行啦,如果有DI的話…),但3、4兩項就比較不符Component
本身的概念,因為Component
應該是一個受體
,因應State
的改變而有不同的顯示,但不應該去主導改變的方式。
但,3、4兩項工作還是要有人做,只是給誰做的問題。那,困難在哪?
困難在dispatch
的取得,改變的方式一個是將Store
保留在大家都拿得到的地方,另一個是透過function mapDispatchToProps(dispatch)
將它保留下來,或是傳給別人。
將Store
保留在大家都拿得到的地方,太危險!透過function mapDispatchToProps(dispatch)
將它傳給別人可能是比較好的做法,所以可以想像,如果使用一個function
接受dispatch
做為參數,來做3、4項的工作,會不會比較好?
function loadTodos(dispatch) {
dispatch(todoActions.showLoading());
const todos = await todoService.pureFetchAllTodos();
dispatch(todoActions.listTodos(todos));
dispatch(todoActions.finishLoading());
}
利用function
接受dispatch
做為參數,就是redux-thunk的主要想法。
Redux Thunk
原本的Action寫法應該像下面這樣
function showLoading() {
return {
type : todoConstants.SENDING_REQUET,
loading : true
}
}
function listTodos(todos) {
return {
type : todoConstants.LOAD_ALL,
todos
}
}
function finishLoading() {
return {
type : todoConstants.FINISHED_REQUEST,
loading : false
}
}
配合需要改變的State
回傳Plain Object
,但使用Thunk
後,會變這樣
function thunkListTodos() {
return async (dispatch, getState) => {
dispatch(showLoading());
let todos = await todoService.pureFetchAllTodos();
dispatch(listTodos(todos));
dispatch(finishLoading());
};
}
回傳一個function
,使用dispatch, getState
作為傳入的參數,然後再把原本寫在Component
裡的邏輯搬過來。
原來的Component
就會變成下面這樣
componentDidMount() {
this.props.thunkListTodos();
}
function mapDispatchToProps(dispatch) {
return {
thunkListTodos : () => {
dispatch(todoActions.thunkListTodos());
}
};
}
把邏輯移出去,Component
變乾淨。
完整的程式可以參考React Thunk Example,以上。