React – Redux-Thunk

總結

Redux-Thunk

  1. 要求Action return一個接受dispatchfunction,而不是一個單純的Plain Object。
  2. 可將各種狀態改變與資料操作的邏輯放在Action

前言

前接

  1. Redux - 1
  2. Redux - 2
  3. Async, Aswait

先說說我認為的Redux運作方式

Redux

  1. Component 建立 Action
  2. Component 呼叫 Store的dispatch,並送入Action當參數。
  3. StoreAction 傳給各個 Reducer,看哪一個Reducer要處理。
  4. Reducer處理完後產生新的 State,再交回給 Store
  5. 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要做的工作

  1. Redux說明哪些State是它要關心的
  2. 因為不想直接操作Store,就只好先把要做的dispatch工作告訴Redux
  3. 操作dispatch來改變狀態
  4. 使用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,以上。

發表迴響

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料