Redux -- 1 action Reducer

一.Action

1.概念

Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的惟一来源。通常来讲你会经过 store.dispatch() 将 action 传到 store。javascript

2.Action 建立函数

Action 建立函数 就是生成 action 的方法。“action” 和 “action 建立函数” 这两个概念很容易混在一块儿,使用时最好注意区分。html

在 Redux 中的 action 建立函数只是简单的返回一个 action:java

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

3.actions.js

/*
 * action 类型
 */

export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'

/*
 * 其它的常量
 */

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}

/*
 * action 建立函数
 */

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index }
}

export function setVisibilityFilter(filter) {
  return { type: SET_VISIBILITY_FILTER, filter }
}

 

二. Reducer 

Action 只是描述了有事情发生了这一事实,并无指明应用如何更新 state。而这正是 reducer 要作的事情。git

1. Action 处理

如今咱们已经肯定了 state 对象的结构,就能够开始开发 reducer。reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。github

(previousState, action) => newState

 之因此将这样的函数称之为reducer,是由于这种函数与被传入 Array.prototype.reduce(reducer, ?initialValue) 里的回调函数属于相同的类型。保持 reducer 纯净很是重要。永远不要在 reducer 里作这些操做:redux

  • 修改传入参数;
  • 执行有反作用的操做,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random()

reduce:api

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce数组

2. 编写Reducer

 reducer 必定要保持纯净。只要传入参数相同,返回计算获得的下一个 state 就必定相同。没有特殊状况、没有反作用,没有 API 请求、没有变量修改,单纯执行计算。dom

明白了这些以后,就能够开始编写 reducer,并让它来处理以前定义过的 action函数

咱们将以指定 state 的初始状态做为开始。Redux 首次执行时,state 为 undefined,此时咱们可借机设置并返回应用的初始 state。

import { VisibilityFilters } from './actions'

const initialState = {
  visibilityFilter: VisibilityFilters.SHOW_ALL,
  todos: []
};

function todoApp(state, action) {
  if (typeof state === 'undefined') {
    return initialState
  }

  // 这里暂不处理任何 action,
  // 仅返回传入的 state。
  return state
}

这里一个技巧是使用 ES6 参数默认值语法 来精简代码。

function todoApp(state = initialState, action) {
  // 这里暂不处理任何 action,
  // 仅返回传入的 state。
  return state
}

如今能够处理 SET_VISIBILITY_FILTER。须要作的只是改变 state 中的 visibilityFilter

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

注意:

  1. 不要修改 state 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state,{ visibilityFilter: action.filter }),由于它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也能够开启对ES7提案对象展开运算符的支持, 从而使用 { ...state, ...newState }达到相同的目的。

  2. 在 default 状况下返回旧的 state遇到未知的 action 时,必定要返回旧的 state

 

3.处理多个 action

还有两个 action 须要处理。让咱们先处理 ADD_TODO

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}

如上,不直接修改 state 中的字段,而是返回新对象。新的 todos 对象就至关于旧的 todos 在末尾加上新建的 todo。而这个新的 todo 又是基于 action 中的数据建立的。

最后,TOGGLE_TODO 的实现也很好理解:

case TOGGLE_TODO:
  return Object.assign({}, state, {
    todos: state.todos.map((todo, index) => {
      if (index === action.index) {
        return Object.assign({}, todo, {
          completed: !todo.completed
        })
      }
      return todo
    })
  })

4. 拆分 Reducer

目前的代码看起来有些冗长:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if(index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}

上面代码可否变得更通俗易懂?这里的 todos 和 visibilityFilter 的更新看起来是相互独立的。有时 state 中的字段是相互依赖的,须要认真考虑,但在这个案例中咱们能够把 todos 更新的业务逻辑拆分到一个单独的函数里:

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: todos(state.todos, action)
      })
    default:
      return state
  }
}

注意 todos 依旧接收 state,但它变成了一个数组!如今 todoApp 只把须要更新的一部分 state 传给 todos 函数,todos 函数本身肯定如何更新这部分数据。这就是所谓的 reducer 合成,它是开发 Redux 应用最基础的模式。

下面深刻探讨一下如何作 reducer 合成。可否抽出一个 reducer 来专门管理 visibilityFilter?固然能够:

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
  case SET_VISIBILITY_FILTER:
    return action.filter
  default:
    return state
  }
}

5. combineReducers

combineReducers() 所作的只是生成一个函数,这个函数来调用你的一系列 reducer,每一个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,而后这个生成的函数再将全部 reducer 的结果合并成一个大的对象。没有任何魔法。正如其余 reducers,若是 combineReducers() 中包含的全部 reducers 都没有更改 state,那么也就不会建立一个新的对象。

最后,Redux 提供了 combineReducers() 工具类来作上面 todoApp 作的事情,这样就能消灭一些样板代码了。有了它,能够这样重构 todoApp

import { combineReducers } from 'redux';

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp;

1. 注意上面的写法和下面彻底等价:

export default function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

 

2.你也能够给它们设置不一样的 key,或者调用不一样的函数。下面两种合成 reducer 方法彻底等价:

const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})
function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}

 

6. reducer.js

import { combineReducers } from 'redux'
import { ADD_TODO, TOGGLE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions'
const { SHOW_ALL } = VisibilityFilters

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp
相关文章
相关标签/搜索