在react項目中使用redux做一個和官網不一樣的todo list

前言

這篇文章是我在學習redux的過程中,在編碼的每一個階段編寫的學習筆記。項目地址:todo App。純react是在normal分支,結合redux的是在redux分支。react(15.6.1)和redux(3.7.2)都是當前(2017.8.4)的最新版本。所有的數據是存儲在localstorage下的,點擊查看demo。因為我的ui是移動端的,pc端沒有做處理,只能在移動端或是chrome下按CTRL+SHIFT+M進行查看。

演示

image
image

概念

action 來描述“發生了什么”

reducers 來根據 action 更新 state

Store 就是把它們聯系到一起的對象

action

Action 本質上是 JavaScript 普通對象。我們約定,action 內必須使用一個字符串類型的 type 字段來表示將要執行的動作。

// action types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const CHANGE_TAB = 'CHANGE_TAB';
const DELETE_TODO = 'DELETE_TODO';

// action constants
export const pageFilters = {
  GO_LIST: 'GO_LIST',
  GO_FORM: 'GO_FORM',
};

在 Redux 中的 action creators只是簡單的返回一個 action:

// action creators
// 添加一個todo任務
export function addTodo(newTodo) {
  return { type: ADD_TODO, newTodo };
}

// 勾選完成/未完成的切換
export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index };
}

// 列表頁與表單頁的切換
export function changeTab(filter) {
  return { type: CHANGE_TAB, filter };
}

// 刪除一個todo
export function deleteTodo(index) {
  return { type: DELETE_TODO, index };
}

Reducer

Action 只是描述了有事情發生了這一事實,并沒有指明應用如何更新 state。而這正是 reducer 要做的事情。

先設計一個state tree,用來存放所有的state。

const stateTree = {
  pageFilter: 'GO_LIST', //當前頁
  todos: [ //任務列表
    {
      text: '11',
      place: '1',
      time: '2:20',
      content: 'dgusdguysdgisdhuis',
      completed: false,
    },
  ],
};

構造一個初始化state

const initialState = {
  pageFilter: pageFilters.GO_LIST,
  todos: [],
};

創建reducer。通過action的type改變state

// reducer一定為純函數,接受state和action,返回state。如果不存在state,則返回最初的state。
// (previousState, action) => newState
function todoApp(state = initialState, action) {
  switch (action.type) {
    case CHANGE_TAB:
    // 這里不能直接修改state,要使用 Object.assign() 新建了一個副本
      return Object.assign({}, state, {
        filter: action.filter,
      });
    default:
      return state;
  }
}

寫一下對其他action的處理,值得注意的是,都不要在原來的state上進行操作!!

function todoApp(state = initialState, action) {
  switch (action.type) {
    case CHANGE_TAB:
      return Object.assign({}, state, {
        filter: action.filter,
      });
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          action.newTodo,
        ],
      });
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if (action.index === index) {
            return Object.assign({}, todo, {
              completed: !todo.completed,
            });
          }
          return todo;
        }),
      });
    case DELETE_TODO:
      return Object.assign({}, state, {
        todos: [].concat(state.todos).splice(action.index, 1),
      });
    default:
      return state;
  }
}

除了CHANGE_TAB其他的action都是用來操作todos的,把它們拆分出來,使代碼結構看起來更加清晰。

// 操作state.todos
function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state.todos,
        action.newTodo,
      ];
    case TOGGLE_TODO:
      return state.todos.map((todo, index) => {
        if (action.index === index) {
          return Object.assign({}, todo, {
            completed: !todo.completed,
          });
        }
        return todo;
      });
    case DELETE_TODO:
      return [].concat(state.todos).splice(action.index, 1);
    default:
      return state;
  }
}

現在我們可以開發一個函數來做為主 reducer,它調用多個子 reducer 分別處理 state 中的一部分數據,然后再把這些數據合成一個大的單一對象。主 reducer 并不需要設置初始化時完整的 state。初始時,如果傳入 undefined, 子 reducer 將負責返回它們的默認值。

function todoApp(state = {}, action) {
  return {
    filter: filter(state.filter, action),
    todos: todos(state.todos, action),
  };
}

調用redux的工具類combineReducers把子reducer整合在一起。

const todoApp = combineReducers({
  filter,
  todos,
});

Store

Store 有以下職責:

  1. 維持應用的 state;
  2. 提供 getState() 方法獲取 state;
  3. 提供 dispatch(action) 方法更新 state;
  4. 通過 subscribe(listener) 注冊監聽器;
  5. 通過 subscribe(listener) 返回的函數注銷監聽器。

創建一個store

import { createStore } from 'redux';
import todoApp from './reduces';
// Redux應用只有一個單一的store
// 根據已有的reducer創建store
// createStore第二個參數是可選的, 用于設置 state 初始狀態
const store = createStore(todoApp);

發起action

// 打印初始state狀態
console.log(store.getState());
// 每次 state 更新時,打印日志
// 注意 subscribe() 返回一個函數用來注銷監聽器
const unsubscribe = store.subscribe(() =>
  console.log(store.getState()),
);
// 發起action
store.dispatch(changeTab(GO_FORM));

// 停止監聽 state 更新
unsubscribe();

可以從console中看到state的變化。

搭配 React

react與redux并沒有什么依賴關系,只不過react允許使用state函數的形式來描述界面。

先安裝react-redux,使用<Provider>把根組件包起來

import { Provider } from 'react-redux';

render(
  <Provider store={store}>
    <Root />
  </Provider>
  ,
  document.getElementById('root'),
);

Provider中的任何一個組件,想要使用store中的數據,就必須connect()對其封裝。

connect

connect() 接收四個參數,它們分別是 mapStateToProps,mapDispatchToProps,mergeProps和options。

mapStateToProps 讀取state

在這里我們使用mapStateToProps這個函數,將store中的數據作為props綁定到組件上。

// 構造一個函數返回需要的state
const mapStateToProps = (state) => {
  const { filter, todos } = state;
  return {
    filter,
    todos,
  };
};
// 使用connect將state作為props傳入root中
export default connect(mapStateToProps)(Root);

這時,<Root/>中的就有了如下的props

{
    dispatch:function dispatch(action),
    filter:"GO_LIST",
    todos:Array(0)
}

分發action

定義mapDispatchToProps()方法接受dispatch()方法,并返回一個回調函數,用來分發action。

// 構造一個函數來分發action
const mapDispatchToProps = dispatch => ({
  goPages: (pageName) => {
    dispatch(changeTab(pageName));
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(Root);

這時再打印一下<Root/>里的props

{
    filter:"GO_LIST",
    goPages:pageName => {…},
    todos:Array(0)
}

在調用時,作為props傳遞給子組件,如:

<AddPage changeTab={() => goPages(pageFilters.GO_LIST)} data={this.state.data} />

相關資料

react官方文檔

redux中文文檔redux官方文檔

Flux標準Action

redux-actions

react-redux詳解

聽說你需要這樣了解 Redux(一)

聽說你需要這樣了解 Redux(二)

聽說你需要這樣了解 Redux(三)

normalizr

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容