🦄 2024 独立开发者训练营,一起创业!查看介绍 / 立即报名(剩余10个优惠名额) →

Redux:Reducers

Actions 描述了在应用里面发生的事情,但是应用的状态(state)具体应该怎么样响应这些动作是 Reducers 的任务。

设计状态

state(状态),指的就是数据。在 Redux 里,应用的所有的状态都会存储在唯一的一个对象里。写代码之前最好先想想这个对象的形状。

比如我们的任务列表应用,会存储两种东西:

  1. 当前所选的可见性过滤器
  2. 任务列表项目

在状态树里会存储数据还有一些 UI(界面) 状态,最好让数据与 UI 状态分开。

{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}

处理动作

我们已经确定了状态对象的样子,现在可以去为它写个 Reducer 了。Reducer 是纯函数(Pure functions),Reducer 会接收两个东西:之前的状态还有一个动作,然后它会返回应用的下一个状态。

(previousState, action) => newState

Reducer 是纯函数,下面这些东西不应该在 Reducer 里出现:

  • 改变了它的参数。
  • 做了些带副作用的事,比如调用了 API。
  • 调用不纯的函数,比如 Date.now() 或 Math.random() 。

以后会介绍怎么样做带副作用的事,现在你要记住的是 Reducer 必须是纯的。给它同样的参数,它要运算并返回下一个状态。无意外,不带副作用,不调用 API,没有改变,只是一个计算。

下面我们就一步一步的教会 Reducer 明白我们之前定义的动作。首先我们要定义一个初始的状态。Redux 第一次调用 Reducer 会带一个 undefined 状态,返回应用的初始状态:

 

import { VisibilityFilters } from './actions'

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

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

  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}

也可以使用 ES2015 函数的默认参数,像这样:

function todoApp(state = initialState, action) {
  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}

再去处理 SET_VISIBILITY_FILTER 动作,它要做的就是改变状态里的 visibilityFilter:

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

注意:

  1. 我们没有直接改变状态,而是创建了一个复制品,这里用了 Object.assign(),也可以使用对象的 Spread 操作符: { ...state, ...newState }。
  2. 在默认的情况下返回之前的状态。这样如果有未知的动作发生的时候,就会返回之前的状态。

处理更多动作

还有两个要处理的动作,跟处理 SET_VISIBILITY_FILTER 动作一样。下面我们要导入 ADD_TODO 还有 TOGGLE_TODO 动作,然后再去改造一下我们的 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
          }
        ]
      })    
    default:
      return state
  }
}

跟之前一样,不要直接改变状态,要返回一个新的对象,新的任务列表就是老的任务列表加上新的任务列表项目,新的任务列表项目用了在动作那里组织好的数据。

再处理下 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
    })
  })

分离 Reducers

代码现在是这样的:

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(可见性过滤器) 是完全独立的,所以可以很容易分离开,下面把更新 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 TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: todos(state.todos, action)
      })
    default:
      return state
  }
}

注意上面的 todos 同样会接收 state(状态),不过这个 state 是一个数组。todoApp 只给它一部分状态去管理,todos 知道怎么样去更新这小部分状态。这就是 Reducer 组合,这是创建 Redux 应用的基础模式。

下面再抽离出一个 visibilityFilter :

const { SHOW_ALL } = VisibilityFilters;

然后再:

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

 

重新再写一下主 Reducer(todoApp),用它组合一下之前我们抽离出来的两个 Reducer(visibilityFiltertodos):

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 visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

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

注意每个 Reducer 都会管理它们自己的那部分全局状态。每个 Reducer 的 state 参数是不一样的,对应的就是它管理的那部分 state。你也可以把 Reducer 放在单独的文件里。

Redux 提供了一个帮手方法:combineReducers(),用它可以这样重写一下组合 Reducer 的代码:

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

上面的代码跟下面这块代码的功能是一样的:

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

也可以给 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)
  }
}

Source Code

reducers.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
Redux
微信好友

用微信扫描二维码,
加我好友。

微信公众号

用微信扫描二维码,
订阅宁皓网公众号。

240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

15260
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点