本文将会不断更新和整理。
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>
网友评论