理解Redux

从 React 说起

刚开始接触 React 的时候其实是感到非常惊艳的,在 React 的架构下可以像搭积木一样把前端的视图组件组合在一起,非常好玩,React 这种模块化的结构保证了组件内部的高度自治性,使得前端开发高度正交化。以至于刚刚使用 React 的时候简直觉得是在上帝之手的帮助之下写前端,触手丝滑,效率飞起,但是用了一段时间之后就很快发现了一些问题,比如父组件可以通过给子组件赋值 props,但是子组件向父组件传值却只能通过较为复杂的双向绑定,如果要向不是父子关系的组件传值怎么办呢?React 的文档会告诉你只能再 copy 一个组件过来了。EXM?组件之间传值这么常见的操作 React 却只能给出一个这么简单粗暴的办法?负分!差评!

但是知道了 Redux 这么一个神奇的存在之后,感觉 React 终于有救了,在 Redux 的框架之下,所有的状态被集中起来统一管理,使得应用内的数据完美地同步起来,再也不用担心组件之间的通讯问题了。

当然这里要提一下,Redux 本身是一个单独的框架,跟 React 没什么关系,但是由于在 React 的框架下视图和 state 和 view 是一一对应的,这样就使得 Redux 更适用于 React 环境下的开发。

Redux 的基本思想

Redux 是一个基于Flux思想实现的一个针对web应用的状态管理库,在Redux 里 Web 应用被视为一个有穷状态机,在这个状态机里所有状态的变化都是可以追溯甚至是可以撤销的,为了实现这样的机制,Redux 进行了以下三个约束:

  • 所有的 state 构成一棵 object tree,这棵 object tree 只存在于唯一一个store 中。
  • 所有state都是只读的,唯一改变 state 的方法是触发 action
  • 使用纯函数来执行修改,以保证每次对状态修改的执行结果都是一致的

在 redux 中主要引入了 action、reducer、store 这三个概念,action 用于定义一个请求,reducer 用于根据 action 产生新状态,store 用于存储和管理 state,监听 action,将 action 自动分配给 reducer 并根据 reducer 的执行结果更新 state。

在这里你可以根据字面意思把 store 想象成一个仓库,这个仓库里的货物就是 state ,但是这个仓库的管理员实在是懒得跟自己的客户沟通,就请了好几个助手来帮自己,比如助手 A 专门负责某个手机厂商的请求,助手B专门负责电脑厂商的请求,他们计算好进出货量之后通知管理员,管理员如果接到手机厂商的请求就把他分配给 A,接到电脑厂商的请求就分配给B,得到 A 和 B 的结果之后管理员就可以马上更新仓库了,在这里这些助手就是 reducer,像手机和电脑厂商的请求就是 action。执行任务分配的这个过程就是 store.despatch(action) 函数 。

Redux 的基本概念

Action

action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。用来表明一个事件的发生,但并不对状态如何修改做任何描述。一个action 的结构是一个 javascript 普通对象,一个 action 的结构如下

{
  //type字段用于标识action的类型,一般用一个字符串来表示
  type: 'ADD_TODO', 
  //text是用户自定义的字段,一般用来传递和状态修改相关的参数
  text: 'Build my first Redux app'
}

Reducer

action 只是描述了有事情发生了这一事实,但是并没有指名如何更新 state,reducer 就是对状态修改过程的描述,但是需要注意的有以下两点:

  • 由于状态是只读的,reducer 本身并不能真正实现状态的修改,而是只把新状态作为返回值返回。

  • 为了确保每次对状态修改的结果都是一致的,reducer 必须是一个纯函数,也就是说,只要是同样的输入,必定得到同样的输出。纯函数需要遵循以下约束:

    不得改写参数
    不能调用系统 I/O 的API
    不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
    

    一个 reducer 的结构如下

/* 在这个 reducer 中,对于一个类型为 ADD_TODO 的 action,返回的新状态是在传入状态数组中追加了一个元素 */
function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
      default:
          return state
  }
}

Store

在定义了描述事件发生的 action 和描述状态修改方案的 reducer 之后,如何把它们联系到一起真正实现状态的改变呢?这就要牵扯到 store 啦,前面已经提过 redux 的原则之一就是所有的 state 构成的对象树都存到了 store 中,除此之外 store 还有以下功能:

  • 维持应用的 state
  • 提供 getState() 方法获取 state
  • 提供 dispatch(action)方法更新 state;
  • 通过 subscribe(listener)注册监听器;
  • 通过 subscribe(listener)返回的函数注销监听器。

创建一个 store 其实很简单:

import { createStore } from 'redux'
import reducers from './reducers'
let store = createStore(reducers)

createStore() 的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。

let store = createStore(todoApp, window.STATE_FROM_SERVER)                                                                                                                                                                                                                                                                                                                                                                                                                                   

接下来就可以简单实现一个创建处理 action 的过程了

import { createStore } from 'redux'
const ADD_TODO = 'ADD_TODO' 
//创建一个 action 
const simpleAction1 = {
  type: 'ADD_TODO', 
  text: 'Build my first Redux app'
}
const simpleAction2 = {
  type: 'ADD_TODO', 
  text: 'Build my second Redux app'
}
//创建一个 reducer, 功能为收到类型为 ADD_TODO 的 action 后为在 state 中添加一个条目
function reducer(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
      default:
          return state
  }
}
//创建一个初始状态为空数组的 store 
let store = createStore(reducer,[])

// 打印初始状态
console.log(store.getState())

// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
)
// 发起一个 action, 执行之后会更新状态,由于注册了监听器你可以看到每次更新都会打印当前状态
store.dispatch(simpleAction1)
store.dispatch(simpleAction1)

// 停止监听 state 更新
unsubscribe();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容