redux

redux的设计思想来源于Flux,但是要比Flux的模型要更加的简化。

在redux中,数据流是单向流动的。在redux中有三个主要的概念

在redux中,也有三大原则,这三原则其实是和之前提到的三个主要概念是对应的

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。

基础

Store

store 是应用状态 state 的管理者。 state 是应用的黄台,一般本质上是一个普通对象,比如一个计数器的计数次数这样的对象属性。 store 包含下列四个函数:

store和state的关系是 state = store.getState()

Redux 规定,一个应用只应有一个单一的 store , 其管理着唯一的应用状态 state Redux 还规定,不能直接修改应用的状态 state , 也就是说,下面的行为是不允许的

var state = store.getState()
state.counter = state.counter + 1 // 禁止在业务逻辑中直接修改 state

要改变 state ,正确的做法是 dispatch 一个action

store是通过调用Redux中的 createStore 函数生成的:

import { createStore } from 'redux'
...
const store = createStore(reducer, initialState) // store 是靠传入 reducer 生成的哦!

现在您只需要记住 reducer 是一个 函数,负责更新并返回一个新的 state而 initialState 主要用于前后端同构的数据同步(详情请关注 React 服务端渲染)

Action

% action

Action 是把数据从应用或者视图传到store的有效负载。他是store 数据的唯一来源。 一般来说会通过store.dispatch() 函数将action传到store。

 action 只是一个包含 type 属性的普通对象。例如 { type: 'INCREMENT' } 。这个 type 是我们实现用户轻微追踪的关键,一个更复杂的例子:

    {
      type: 'ADD_TODO',
      payload: {
        id: 1,
        content: '待办事项1',
        completed: false
      }
    }

当然,action的形式是多种多样的,唯一的约束就是必须包含一个 type 属性 以下 action 都是合法的:

// 如下都是合法的,但就是不够规范 
{
  type: 'ADD_TODO',
  id: 1,
  content: '待办事项1',
  completed: false
}
{
  type: 'ADD_TODO',
  abcdefg: {
    id: 1,
    content: '待办事项1',
    completed: false
  }
}

规范如下

规则

一个action必须的属性:

  • 一定是一个普通的JavaScript对象
  • 这个对象有type属性

一个action可能拥有的属性:

  • error属性
  • payload属性
  • meta属性

一个action不要有除了type、payload、meta、meta之外的属性

type

是字符串类型的数据,用于识别一个action

payload

可选的属性payload属性可以是任何类型的值。它代表了action的有效负载。不是action的type或者状态的信息的内容都应该包含在payload中。 举个例子,如果error字段的属性是true,那么payload的内容就应该是错误对象。

error

这个字段是布尔类型,这个字段是true的时候,那么payload就是error对象 error是null或者undefined的时候,action也不是error

meta

其他非负载的属性都可以存在meta中

& Action Creator

Action Creator 可以是同步的,也可以是异步的。

action创建函数就是生成action的方法。 “action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。

// 本代码块记为 code-4
var id = 1function addTodo(content) {
  return {
    type: 'ADD_TODO',
    payload: {
      id: id++,
      content: content, // 待办事项内容
      completed: false  // 是否完成的标识
    }
  }
}

然后再看看实际应用

<--! 本代码块记为 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>

action 虽然没有强制的规范,但是在 store.dispatch(action) 之后,Redux 会明确知道是提取 action.payload ,并且是对应写入到 state。todos 数组中,这都是 Reducer 的功劳。

Reducer

Reducer 必须是同步的纯函数

reducer 的实质是一个纯函数,reducers 指定了应用状态的变化如何响应 actions 并发送到 store,actions 只是描述了 有事情发生 这个试试,并没有描述应用应该如何更新 state 。而描述如何更新 store 是reducer的工作。

reducer会更具 action.type 来更新 state 并返回 nextState 最后会用 reducer 的返回值 nextState 完全替换掉 原来的 state

var initState = {
  counter: 0,
  todos: []
}

function reducer(state, action) {
  // ※ 应用的初始状态是在第一次执行 reducer 时设置的 ※
  if (!state) state = initState
  
  switch (action.type) {
    case 'ADD_TODO':
      var nextState = _.cloneDeep(state) // 用到了 lodash 的深克隆
      nextState.todos.push(action.payload) 
      return nextState

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

§ 总结

Action Creator => action => store.dispatch(action) => reducer(state, action) => 原 state state = nextState

⊙ 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 | 将修改后的记录写回数据库

进阶

Redux API

API 汇总

Redux 有五个 API,分别是:

createStore 生成的 store 有四个 API,分别是:

§ [compose(…functions)][compose]

组合函数


<!DOCTYPE html>
<html>
<head>
  <script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script>
</head>
<body>
<script>
function func1(num) {  
console.log('func1 获得参数 ' + num);
return num + 1;
}

function func2(num) {
console.log('func2 获得参数 ' + num);
return num + 2;
}  

function func3(num) {
console.log('func3 获得参数 ' + num);
return num + 3;
}

// 有点难看(如果函数名再长一点,那屏幕就不够宽了)
var re1 = func3(func2(func1(0)));
console.log('re1:' + re1);

console.log('===============');

// 很优雅
var re2 = Redux.compose(func3, func2, func1)(0);
console.log('re2:' + re2);
</script>
</body>
</html>

控制台输出

func1 获得参数 0
func2 获得参数 1
func3 获得参数 3
re1:6
===============
func1 获得参数 0
func2 获得参数 1
func3 获得参数 3
re2:6
§ createStore(reducer, initialState, enhancer)
§ combineReducers(reducers)
§ bindActionCreators(actionCreators, dispatch)

var actionsCreators = Redux.bindActionCreators(
{ addTodo: addTodo },
store.dispatch // 传入 dispatch 函数
)

$('#btn').on('click', function() {
var content = $('#todoInput').val()
actionCreators.addTodo(content) // 它会自动 dispatch
})d
§ applyMiddleware(…middlewares)
<!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 || { counter: 0 };

  switch (action.type) {
    case 'INCREMENT':
      return { counter: state.counter + 1 };
    case 'DECREMENT':
      return { counter: state.counter - 1 };
    default:
      return state;
  }
}

function printStateMiddleware(middlewareAPI) {
  return function (dispatch) {
    return function (action) {
      console.log('dispatch 前:', middlewareAPI.getState());
      var returnValue = dispatch(action);
      console.log('dispatch 后:', middlewareAPI.getState(), '\n');
      return returnValue;
    };
  };
}

var enhancedCreateStore = Redux.applyMiddleware(printStateMiddleware)(Redux.createStore);
var store = enhancedCreateStore(reducer);

store.dispatch(inc());
store.dispatch(inc());
store.dispatch(dec());
</script>
</body>
</html>

控制台输出:

dispatch 前:{ counter: 0 }
dispatch 后:{ counter: 1 }

dispatch 前:{ counter: 1 }
dispatch 后:{ counter: 2 }

dispatch 前:{ counter: 2 }
dispatch 后:{ counter: 1 }

实际上,上面生成 store 的代码可以更加优雅:

/** 本代码块记为 code-10 **/
var store = Redux.createStore(
  reducer,
  Redux.applyMiddleware(printStateMiddleware)
)

如果有多个中间件以及多个增强器,还可以这样写(请留意序号顺序):

重温一下 createStore 完整的函数签名:function createStore(reducer, preloadedState, enhancer)

/** 本代码块记为 code-11 **/
import { createStore, applyMiddleware, compose } from 'redux'

const store = createStore(
  reducer,
  preloadedState,    // 可选,前后端同构的数据同步
  compose(           // 还记得吗?compose 是从右到左的哦!
    applyMiddleware( // 这货也是 Store Enhancer 哦!但这是关乎中间件的增强器,必须置于 compose 执行链的最后
      middleware1,
      middleware2,
      middleware3
    ),
    enhancer3,
    enhancer2,
    enhancer1
  )
)

为什么会支持那么多种写法呢?在 createStore 的源码分析的开头部分,我省略了一些代码,现在奉上该压轴部分:

/** 本代码块记为 code-12 **/
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  // 这里就是上面 code-10 的情况,只传入 reducer 和 Store Enhancer 这两个参数
  enhancer = preloadedState
  preloadedState = undefined
}

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }
  // 存在 enhancer 就立即执行,返回增强版的 createStore <--------- 记为【锚点 12-1】
  return enhancer(createStore)(reducer, preloadedState)
}

if (typeof reducer !== 'function') {
  throw new Error('Expected the reducer to be a function.')
}

// 除 compose 外,createStore 竟然也在此为我们提供了书写的便利与自由度,实在是太体贴了

react-redux


import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { Router, Route, browserHistory } from 'react-router';
import App from './App';

const Root = ({ store }) => (
  <Provider store={store}>
    <Router history={browserHistory}>
      <Route path="/(:filter)" component={App} />
    </Router>
  </Provider>);

Root.propTypes = {
  store: PropTypes.object.isRequired,};

export default Root;