全面解析Redux

本文将会不断更新和整理。

Store

首先要区分 store和 state?
state是应用状态,一般本质上是一个普通对象,例如:我们有一个 Web APP,包含 计数器 和 待办事项 两大功能,那么我们可以为该应用设计出对应的存储数据结构(应用初始状态):
<pre>// 应用初始 state,本代码块记为 code-1
{
counter: 0,
todos: []
}</pre>

store是应用状态state的管理者,包含下列四个函数:

  • 1、getState() # 获取整个 state
  • 2、dispatch(action) # ※ 触发 state 改变的【唯一途径】※
  • 3、subscribe(listener) # 您可以理解成是 DOM 中的 addEventListener
  • 4、replaceReducer(nextReducer) # 一般在 Webpack Code-Splitting 按需加载的时候用

二者的关系是:state = store.getState()
Redux 规定,一个应用只应有一个单一的 store,其管理着唯一的应用状态 state
Redux 还规定,不能直接修改应用的状态 state,也就是说,下面的行为是不允许的:
<pre>var state = store.getState();
state.counter = state.counter + 1 // 禁止在业务逻辑中直接修改 state</pre>

若要改变 state,必须 dispatch一个 action,这是修改应用状态的不二法门
<pre>现在您只需要记住 action只是一个包含 type ** 属性的普通对象**即可例如 { type: 'INCREMENT' }</pre>

上面提到,state是通过 store.getState()获取,那么 store又是怎么来的呢?想生成一个 store,我们需要调用 Redux 的 createStore:
<pre>import { createStore } from 'redux'...
const store = createStore(reducer, initialState) // store 是靠传入 reducer 生成的哦!</pre>

Provider

可以将从createStore返回的store放入connect中,使子集可以获取到store并进行操作。Provider 也是 react-redux 提供的工具组件。
<pre>React.render(
<Provider store={store}>
{() => <MyRootComponent />}
</Provider>, rootEl);</pre>

Provider应该是你的 React Components 树的根组件。由于 React 0.13 版本的问题,Provider的子组件必须是一个函数,这个问题将在 React 0.14 中修复。Provider 和 connect 函数的配合,使得 React Component 在对 Redux 完全无感的情况下,仅通过 React 自身的机制来获取和维护应用程序的状态。

Action

上面提到,action(动作)实质上是包含 type属性的普通对象,这个 type是我们实现用户行为追踪的关键,例如:增加一个待办事项 的 action可能是像下面一样:
<pre>//本代码块记为 code-2
{
type: 'ADD_TODO',
payload: {
id: 1,
content: '待办事项1',
completed: false
}
}</pre>

当然,action的形式是多种多样的,唯一的约束仅仅就是包含一个 type属性罢了也就是说,下面这些 action都是合法的:
<pre>//如下都是合法的,但就是不够规范
{
type: 'ADD_TODO',
id: 1,
content: '待办事项1',
completed: false
}
{
type: 'ADD_TODO',
abcdefg: {
id: 1,
content: '待办事项1',
completed: false
}
}</pre>

虽说没有约束,但最好还是遵循之前flux规范
如果需要新增一个代办事项,实际上就是将 code-2中的 payload“写入” 到 state.todos 数组中(如何“写入”?在此留个悬念):
<pre>//本代码块记为 code-3
{
counter: 0,
todos: [{
id: 1,
content: '待办事项1',
completed: false
}]
}</pre>

刨根问底,action是谁生成的呢?

Action Creator

Action Creator 可以是同步的,也可以是异步的。顾名思义,Action Creator 是 action的创造者,本质上就是一个函数,返回值是一个 action(对象)例如下面就是一个 “新增一个待办事项” 的 Action Creator:
<pre>//本代码块记为 code-4
var id = 1;
function addTodo(content) {
return {
type: 'ADD_TODO',
payload: {
id: id++,
content: content, // 待办事项内容
completed: false // 是否完成的标识
}
}
}</pre>

将该函数应用到一个表单(假设 store为全局变量,并引入了 jQuery ):
<pre>//本代码块记为 code-5
<input type="text" id="todoInput" />
<button id="btn">提交</button>
<script>
$('#btn').on('click', function() {
var content = $('#todoInput').val() // 获取输入框的值
var action = addTodo(content) // 执行 Action Creator 获得 action
store.dispatch(action) // 改变 state 的不二法门:dispatch 一个 action!!!
})
</script></pre>

在输入框中输入 “待办事项2” 后,点击一下提交按钮,我们的 state就变成了:
<pre>//本代码块记为 code-6
{
counter: 0,
todos: [{
id: 1,
content: '待办事项1',
completed: false
}, {
id: 2,
content: '待办事项2',
completed: false
}]
}</pre>

通俗点讲,Action Creator 用于绑定到用户的操作(点击按钮等),其返回值 action用于之后的 dispatch(action)
刚刚提到过,action明明就没有强制的规范,为什么 store.dispatch(action)之后,Redux 会明确知道是提取 action.payload,并且是对应写入到 state.todos数组中?又是谁负责“写入”的呢?悬念即将揭晓...

Reducer(必须是同步的纯函数)

用户每次 dispatch(action)后,都会触发 reducer的执行,reducer的实质是一个函数,根据 action.type来更新 state并返回 nextState,最后会用 reducer的返回值 nextState完全替换掉原来的 state。
注意:上面的这个 “更新” 并不是指 reducer可以直接对 state进行修改,Redux 规定:须先复制一份 state,在副本 nextState上进行修改操作例如,可以使用 lodash 的 deepClone,也可以使用 Object.assign / map / filter/ ...等返回副本的函数
在上面 Action Creator 中提到的 待办事项的 reducer大概是长这个样子 (为了容易理解,在此不使用 ES6 / Immutable.js):
<pre>/** 本代码块记为 code-7 **/
var initState = {
counter: 0,
todos: []
}
function reducer(state, action) {
// ※ 应用的初始状态是在第一次执行 reducer 时设置的(除非是服务端渲染) ※
if (!state) state = initState
switch (action.type) {
case 'ADD_TODO':
var nextState = _.deepClone(state) // 用到了 lodash 的深克隆
nextState.todos.push(action.payload)
return nextState

default:
// 由于 nextState 会把原 state 整个替换掉
// 若无修改,必须返回原 state(否则就是 undefined)
return state
}
}</pre>

小结

  • store由 Redux 的 createStore(reducer)生成
  • state通过 store.getState()获取,本质上一般是一个存储着整个应用状态的对象
  • action本质上是一个包含 type属性的普通对象,由 Action Creator (函数) 产生,改变 state必须 dispatch一个 action
  • reducer本质上是根据 action.type来更新 state并返回 nextState的函数
    reducer 必须返回值,否则 nextState即为 undefined
  • 实际上,state就是所有 reducer返回值的汇总(本教程只有一个 reducer,主要是应用场景比较简单)

Redux 与传统后端 MVC 的对照

Redux 传统后端 MVC
store 数据库实例
state 数据库中存储的数据
dispatch(action) 用户发起请求
action: { type, payload } type表示请求的 URL,payload表示请求的数据
reducer 路由 + 控制器(handler)
reducer中的 switch-case分支 路由,根据 action.type路由到对应的控制器
reducer内部对 state的处理 控制器对数据库进行增删改操作
reducer返回 nextState 将修改后的记录写回数据库

最简单的例子

<pre><!DOCTYPE html>
<html>
<head>
<script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script></head>
<body>
<script>
/** Action Creators */
function inc() {
return { type: 'INCREMENT' };
}
function dec() {
return { type: 'DECREMENT' };
}
function reducer(state, action) {
// 首次调用本函数时设置初始
state state = state || { counter: 0 };
switch (action.type) {
case 'INCREMENT':
return { counter: state.counter + 1 };
case 'DECREMENT':
return { counter: state.counter - 1 };
default: return state; // 无论如何都返回一个 state
}
}
var store = Redux.createStore(reducer);
console.log( store.getState() ); // { counter: 0 }
store.dispatch(inc());
console.log( store.getState() ); // { counter: 1 }
store.dispatch(inc());
console.log( store.getState() ); // { counter: 2 }
store.dispatch(dec());
console.log( store.getState() ); // { counter: 1 }
</script>
</body>
</html></pre>

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

推荐阅读更多精彩内容