iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 29
1
Modern Web

認真學前端開發 - 以 TodoList 為例系列 第 29

Day29 - 使用 React-Redux 來管理狀態 [part3]

放置 redux 檔案夾

雖然 TodoListApp 目前沒有很多 state ,但可能以後會增加很多 reducer 來處理別的狀態

在 redux 中 store 存放的狀態也叫做 state 不要跟 react class component 搞混囉!

所以我的把跟 redux 有關的都拉出來放!

src 底下創一個檔案夾叫做 provider 底下再創一個 todo 裝用的檔案夾

裡面我通常會放以下這些 js 檔案

src
 |- provider
     |- todo
         |- reducer.js
         |- constants.js
         |- actions.js
         |- selector.js

現在要使用 combineReducer 來將 todo reducer 合在一起

combineReducer

combineReducer 可以幫助解決當 state 越來越複雜的時候,可以將複雜的 reducer 拆成比小的模組,然後再使用 combineReducer 將他們合起來

src/reducer.js 改成這樣

import { combineReducers } from "redux";

import {
  KEY,
} from './provider/todo/constants';
import reducer from './provider/todo/reducer';

export default combineReducers({
  [KEY]: reducer,
})

怎麼寫 reducer?

寫法很像 day26component state 的感覺,僅需要寫改變 state 的 handler

像是之前的 code 寫得很多 setState 將 list 存放在 component state 裡面

改成 redux 也很簡單,就是將它們搬到 reducer 裡面,但呼叫 reducer 就要使用 action 來做更新

以取得列表為例

先前寫的是這樣子的 code

componentDidMount() {
  this.setState({
    isLoading: true,
  })
  FirebaseService.getTodoList()
    .then((list) => {
      console.log(list)
      this.setState({
        list,
        isLoading: false,
      })
    }) 
}

所以現在呼叫取得 list 的方法就要改變

前天看到的這個 react component 要改變 state 就要改成發 action 出去

所以可以想像這樣

componentDidMount() {
  this.props.fetchTodoList() // fetchTodoList 是 action
}

action 是一個有 type 的物件,可以寫在 action.js

但原本的 getList 裡有一個 非同步的請求 去拿列表,現在改成一個 action

所以昨天安裝的 redux-thunk middleware 就可以幫助我處理非同步的事件

我的寫法像是這樣

// action creator 中 再回傳一個 function middleware 就會將 store 的 dispatch 帶進來給予使用,這樣子就可以一直 dispatch action 出去了
export const fetchTodoList = () => dispatch => {
  // 更新頁面讀取狀態
  dispatch(setLoading(true))
  FirebaseService.getTodoList()
    .then((list) => {
      // 非同步更新 state
      dispatch({
        type: SET_TODO_LIST,
        payload: list,
      })
    })
}

接下來就是在 reducer 中處理 SET_TODO_LIST 的這個 action

provider/todo/reducer.js

case SET_TODO_LIST:
  // 取消讀取狀態
  const updatedState = {
    ...state,
    isLoading: false,
  }

  // 遇到錯誤
  if (error) {
    return updatedState;
  }

  // 將 list 存取或 merge
  return {
    ...updatedState,
    list: action.payload,
  };

reducer 跟 action 大概是這樣運作的~

connect 連接 store 和發 action

寫了 reducer 但還是不能動,因為要把 View 和 store 連接起來

redux-react 提供了 connect 的 HOC (higher order component) 幫助我們將 state 和 action 轉成 props 帶到 react component 裡面

connect 的 method 如下

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

mapStateToProps 是一個 function 會將 state 轉換成 component 裡需要的 props

將其寫成需要連結到的 state 像是這樣

export const getList = (state) => state[KEY].list
export const getIsLoading = (state) => state[KEY].isLoading

const mapStateToProps = (state) => {

  // 取得列表
  // 取得讀取狀態
  return {
    list: getList(state),
    isLoading: getIsLoading(state),
  }
};

mapDispatchToProps 是一個 function 會將 dispatch 出去的 function 轉換成 component 裡需要的 props ,這樣的動作的稱之為 action creator

將它寫成這樣

const mapDispatchToProps = (dispatch) => ({
  handleFetchTodoList: () => dispatch(fetchTodoList()),
  handleUpdate: (id, item) => dispatch(updateItem(id, item)),
  handleAdd: (name) => dispatch(addItem(name)),
  handleDelete: (id) => dispatch(deleteItem(id)),
});

connect 的使用

const withConnect = connect(mapStateToProps, mapDispatchToProps);

呼叫完會拿到一個 function 然後再使用這個 function 將 component 放入

withConnect(Screen)

這邊如果有很多像 connect 這樣子的 HoC 的話可以用 compose 來組合

export default compose(
  connect(mapStateToProps, mapDispatchToProps),
)(Container)

接下來將 state 都改成從 props

  render() {
    const {
      // redux State
      list,
      isLoading,

      // redux action
      handleAdd,
      handleUpdate,
      handleDelete,
      // state 改成 props
    } = this.props;

    return (
      <div className="container">
        <ActionController
          handleAdd={handleAdd}
        />
        <LoadingSpinner isLoading={isLoading}/>
        <TodoList
          list={list}
          handleUpdate={handleUpdate}
          handleDelete={handleDelete}
        />
      </div>
    )
  }

就大功告成了!

其他的 CRUD Action 和 reducer 寫法就跟 fetchList 雷同就不把 code 貼上來了
有興趣可以看完整代碼

小技巧

昨天有安裝 redux-devtool

所以開發 redux 的時候可以時不時打開來看 store 裡面存放什麼資料!


這樣改完發現很多 code 是從原本 Component 裡面的 function 搬到 actionreducer 中,沒有像相中的難,但要做的事是將邏輯拆散到 action, constants, selector 和 reducer 中,像 TodoListApp 這個 state 僅有使用過一次的話,好像使用 redux 反而寫了一堆 code 來做事!不過這樣寫真的讓每個模組關注的地方都很專一,Component 的邏輯處理變少了,可讀性也增加了許多!


上一篇
Day28 - 使用 React-Redux 來管理狀態 [part2]
下一篇
Day30 - 完賽感言與回顧
系列文
認真學前端開發 - 以 TodoList 為例30

尚未有邦友留言

立即登入留言