Redux is a predictable state container for JavaScript apps
简单来讲,Redux
是一个管理应用状态的框架javascript
前端开发中,本质的问题就是将 server -> client 的输入,变成 client -> user 输入;再将 user -> client 的输入,变成 client -> server 的输入。html
在 client 中,前端的角色其实大概能够当作一个"转换器"。前端
举个简单的例子,后端传过来的是一个 json 格式的数据,这个 json
格式,实际上是在计算机范畴内的,真正的终端用户并不知道什么是json
,更不知道要如何修改json
,保存本身的信息。因此,这个时候就须要像上面说的把 json
转换为页面上的内容和元素;另外,随着用户的一系列操做,数据须要随时更新保存到服务端。整个的这个过程,可能会很复杂,数据和数据之间会存在联动关系。java
这个时候,就须要有一个东西,从更高的层面来管理全部的这些状态,因而有了 mvc
,状态保存在model
,数据展现在view
,controller
来串联用户的输入和数据的更新。可是这个时候就会有个问题,理想状况下,咱们默认全部的状态更新都是由用户的操做(也能够理解为用户的输入)来触发的,但实际状况中,会触发状态更新的不只仅是单纯的用户操做,还有多是用户操做带来的后果,在举个例子:react
页面上有个异步获取信息
的按钮,用户能够点击这个按钮,那么用户点击这个按钮以后,会发生:git
按钮状态变为 pending --> 获取成功,按钮状态变成 success | |--> 获取失败,按钮状态变成 error
这里改变success/error
状态的并非用户输入,而是服务端的返回,这个时候,就须要在 controller
里面 handle 服务端的返回。这只是个简单的例子,若是相似的状况发生了不少以后,每次输入和输出将变得难以预测,难以预测的后果就是很容易出现 bug,程序的健壮性降低。github
让每一步输入和输出可预测,可预测才能可测试,可测试才能保证健壮性。json
因而,这个时候出现了React
和Flux
。redux
Flux
的核心思想就是维护一个单向数据流,数据的流向永远是单向的,因此每一个步骤即是可预测的,程序的健壮性获得了保证。后端
React
的 jsx
能够将前端的 UI 部分变成了一层层套用的方法,再举个例子,以前写 html 是这样的
<div> <span>foo</span> </div>
若是状态改变以后,大部分状况下咱们是将某个片断的 html 用改变的状态从新拼一遍,而后替换到原有的 dom 结构里。
可是,用了 jsx
以后,你的代码将变成这样:
div(span('foo'))
变成了一个函数,这个函数的输出就是上面的那段 html,因此整个 UI 变成了一个可输入输出的结构,有了输入和输出,就是一个完整的可预测的结构了,可预测,也就是表明可测试了。
在使用Flux
的过程里,当应用的结构变得复杂以后,会显得力不从心,虽然数据流仍是单向,可是Flux
的总体流程有两个比较关键的点:
emit
handle emit
当数据结构和输入输出变得复杂的时候,每每会定义不少个 store,可是每每 store 之间仍是会有依赖和关联。
这个时候,handle 的过程会变得很臃肿,难以理解。
而后,Redux
就出场了。
Flux
的思路能够理解为多个store
组成了一个完整的 App;Redux
的思路则是一个完整的store
对应一个完整的 App。
Redux
相比Flux
,多抽象出了一个reducer
的概念。这个reducer
只负责状态的更新,而且会返回一个新的状态对象,整个 App 从结构上看起来,没有一个一直保存/更新的状态(使用Flux
每一个store
都是一直保存住的,而后在此基础上进行更新),Redux
中的数据更像是一个流程。
另外,还有一点比较重要的是,由于没有了一个一直保存/更新的状态对象,因此在 component
中的 handle
也就没有意义了,经过react-redux
能够彻底实现一个顺畅的数据流。
这里举个简单的例子,若是咱们更新一个订单,订单里有这么几项:
其中地址影响运费,运费影响总价;另外,商品数量也会影响总价
使用Flux
的话,咱们一般会分解成这样几个store
:
其中 address
和items
的更新会触发deal.amount
的更新,完整的交易信息会同步到deal
中。
在component
里,咱们会handel
全部这些store
的emit
,而后再进行setState
以更新 UI 部分。
使用Redux
的话,咱们会分解成这样几个reducer
:
其中address
只负责address
的更新,item
只负责items
的更新,deal
会响应address
和item
中跟交易相关的更新,实现改变价格和订单地址的操做。
可是并不须要在component
中再hanle
每一个部分更新以后的emit
。数据更新了,页面就会本身变化。
接下来,咱们看看Redux
是如何实现的。
查看Redux
的github,会发现Redux
的代码异常的精简,仅仅包含了这几个部分:
其中的utils/
和index.js
咱们并不须要关心,只要看接下来的几部分就能够。
另外,由于咱们的大部分场景仍是搭配React
来使用Redux
,因此这里咱们顺便搭配 react-redux
来看下
在react-redux
中,咱们关心的更少,只有:
这两部分而已。
拿一个真正的实例来看,咱们要作一个简单的订单,目录结构是这样的:
|- dealReducer.js |- dealActions.js |- dealStore.js |- dealApp.js |- main.js
先看代码:
import React from 'react' import ReactDom from 'react-dom' import { Provider } from 'react-redux' import configureStore from './dealStore' import DealApp from './dealApp' let store = configureStore() ReactDom.render( ( <Provider store={ store }> <DealApp /> </Provider> ), document.getElementById('app'))
这个部分比较简单,首先是调用了dealStore
中的方法,生成了一个store
,而后调用了react-redux
中的Provider
把这个store
绑定到了Provider
上。
咱们先看 Provider
的代码:
咱们只看下核心的部分:
export default class Provider extends Component { getChildContext() { return { store: this.store } } constructor(props, context) { super(props, context) this.store = props.store } render() { return Children.only(this.props.children) } }
其实最核心就是getChildContext
方法,这个方法在每次props
和state
被调用时会被触发,这里更新了store
仍是先看代码:
import React, { Component, PropTypes } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import * as dealActions from 'deal/actions/dealActions' import * as addressActions from 'deal/actions/addressActions' class DealApp extends Component { // some code } function mapStateToProps(state) { return { 'deal': state.dealReducer, 'address': state.addressReducer, } } function mapDispatchToProps(dispatch) { return { 'dealActions': bindActionCreators(dealActions, dispatch), 'addressActions': bindActionCreators(addressActions, dispatch), } } export default connect(mapStateToProps, mapDispatchToProps)(DealApp)
从代码能够看到,比通常的 react component
多了对connect
的调用,以及mapStateToProps
和mapDispatchToProps
两个方法。
因此,接下来看下这个connect
是什么
来看下核心部分的代码:
// some code componentDidMount() { this.trySubscribe() }, trySubscribe() { if (shouldSubscribe && !this.unsubscribe) { this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange() } }, handleChange() { if (!this.unsubscribe) { return } const storeState = this.store.getState() const prevStoreState = this.state.storeState if (pure && prevStoreState === storeState) { return } if (pure && !this.doStatePropsDependOnOwnProps) { const haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this) if (!haveStatePropsChanged) { return } if (haveStatePropsChanged === errorObject) { this.statePropsPrecalculationError = errorObject.value } this.haveStatePropsBeenPrecalculated = true } this.hasStoreStateChanged = true this.setState({ storeState }) }
能够看到,这几个方法用到了store
中的getState
和subscribe
这几个方法。而且在handleChange
中,实现了在Flux
中须要人肉实现的setState
方法。
既然在上面的connect
中,用到了store
,那么就来看看dealStore
的内容:
import { createStore, applyMiddleware, compose } from 'redux' import thunk from 'redux-thunk' import dealReducers from 'deal/reducers/dealReducer' let creator = compose( applyMiddleware(thunk), applyMiddleware(address), )(createStore) export default function configureStore(initState) { const store = creator(dealReducers, initState) return store }
这个文件里用到了redux
中的createStore
, compose
和applyMiddleware
方法。
经过调用能够看到,先是经过applyMiddleware
方法调用了一些middleware
,而后再用compose
将对middleware
的调用串联起来,返回一个方法,先简单列为f(createStore)
,而后这个调用再次返回了一个方法,这里被定义为creator
。经过调用creator
方法,最终生成了 store
。
下面逐个看一下createStore
,compose
,applyMiddleware
这几个方法。
直接看源码:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
这里直接返回了一个接收createStore
做为参数的方法,这个方法中会遍历传入的middleware
,并使用compose
调用store.dispatch
,接下来看一下compose
方法的具体实现。
仍是直接贴源码:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
能够看到 compose
的源码十分精简,整个compose
的做用就是传入一串funcs
,而后返回一个方法,先暂定这个方法名为c
,c
将传入的funcs
按照从右到左的顺序,逐个执行c
传入的参数。
为何要按照从右到左的顺序执行,咱们先按下不表,接下来看 createStore
的源码。
createStore
的源码比较长,这里就不贴了,详情能够见这里。
咱们这里只看下这个方法的输入和输出既可:
export default function createStore(reducer, preloadedState, enhancer) { // code return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
输入有三个,reducer
和preloadState
咱们都属性,可是这个enhancer
是什么呢?
再来看下相关代码:
if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) }
enhancer
能够当作是预先设定的,对createStore
返回对象执行的方法,好比能够给返回的对象添加一些新的属性或者方法之类的操做,就能够放到enhancer
中作。
看到这里,咱们再来看下compose
中为何调用reducerRight
,将方法从右至左执行。
首先,是applyMiddleware
方法获取到传入的createStore
,返回了:
{ ...store, dispatch }
可是这里的dispatch
已经不是creatStore
中返回的store.dispatch
了。这个dispatch
是经过调用compose
将store.dispatch
传入middlewares
中执行的结果。
再回到主线上来,applyMiddleware
返回了一个加强的store
,若是有多个applyMiddleware
的调用,以下所示:
compose( applyMiddleware(A), applyMiddleware(B), applyMiddleware(C) )
咱们的指望的执行顺序固然是A,B,C
这样,因此转换成方法的话,应该是这样
C(B(A()))
使用reducerRight
的话,最早被调用的方法(也就是上面的C
)就会是执行链的最外层的方法,因此要按照从右到左的顺序执行。
至此,Redux
的介绍就先到这里,以后会再写一些关于Redux
周边组件的使用。