redux

在前面文章中写过 react-redux 在项目中的应用,现在来具体讲解一下 redux。

动机

随着前端框架的盛行,许多原来由后端完成的复杂逻辑和交互都转移到前端,前端应用变得越来越重要,单页应用日趋复杂,我们需要管理更多的 state (状态)。这些 state 可能包括服务器的响应、缓存的数据以及 UI 状态。管理不断变化的 state 非常困难,如果一个 model 的变化引起另一个 model 的变化,那么当页面的 view 变化时,就可能引起对应 model 以及另一个 model 的变化,甚至可能会引起另一个页面 view 的变化。这时,state 的变化就不受控制了。通过限制更新发生的时间和方式,redux 让 state 的变化变得可预测。

首先:什么是 redux 呢?

Redux 是 JavaScript 状态容器,提供可预测化的状态管理,可以和很多的界面库一同使用,例如:react,react-native 等等。同时Redux 是一个体小精悍的库,但它相关的内容和 API 都是精挑细选的,足以衍生出丰富的工具集和可扩展的生态系统。

Redux 由 Flux 演变而来,但受 Elm 的启发,避开了 Flux 的复杂性。

安装:

npm install --save redux
redux 的三大原则
  1. 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
  2. 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
  3. 为了描述 action 如何改变 state tree,需要编写 reducers。
设计思想
  1. Web 应用是一个状态机,视图与状态是一一对应的。
  2. 所有的状态,保存在一个对象里面。

基本概念和 API

Action

Action 把数据从应用传到 store。它是 store 数据的唯一来源。一般来说你可以通过 store.dispatch() 将 action 传到 store。可以理解为,Action 表示当前发生的事情。改变 State 的唯一的方法,就是使用 Action。它会运送数据到 store。

const ADD_TODO = 'ADD_TODO'

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

Action 本质上是一个 JavaScript 普通对象。action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作,是 action 的名称。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。

上面代码中,Action 的名称是 ADD_TODO,携带的信息是 Build my first Redux app。

Action 创建函数

Action 创建函数 就是生成 Action 的方法。View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action 创建函数。

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
const action = addTodo('Learn Redux');

redux 中的 Action 创建函数 只是返回了一个 action。

下面是一个完整的 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

Reducer 是一个纯函数,接收旧的 state 和 action,返回新的 state。也就是说,只要是同样的输入,必定得到同样的输出。

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

注意:永远不要在 reducer 里做如下操作:

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

由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象。

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() 新建了一个副本。
  2. 在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。
Reducer 合成

当一个功能的更新业务逻辑是独立的,可以把它拆分为一个单独 reducer,开发一个函数来做为主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。主 reducer 并不需要设置初始化时完整的 state。初始时,如果传入 undefined, 子 reducer 将负责返回它们的默认值,这就是 Reducer 合成,它是开发 Redux 应用最基础的模式。

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)
  }
}

注意:

  1. todos 依旧接收 state,但它变成了一个数组!
  2. 每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

Redux 提供了combineReducers()工具来做上面 todoApp 做的事情。

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

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

下面来看一下完整的 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
Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。

Store 有以下职责:

创建 store
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp, window.STATE_FROM_SERVER)

createStore() 的第一个参数是 reducer,第二个参数是可选的,用于设置 state 的初始状态。

工作流程

严格的单向数据流是 Redux 架构的设计核心。意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。

bg2016091802.jpg

Redux 应用中数据的生命周期遵循下面 4 个步骤:

  1. 用户发出 Action。
store.dispatch(action)
  1. Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State。
let nextState = todoApp(previousState, action)
  1. State 一旦有变化,Store 就会调用监听函数。
store.subscribe(listener);
  1. listener 通过 store.getState() 得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354