一.作用
与Flux一样,作为状态管理层,对单向数据流做强约束

二.出发点
MVC中,数据(Model)、表现层(View)、逻辑(Controller)之间有明确的界限,但数据流是双向的,在大型应用中尤其明显。一个变化(用户输入或者内部接口调用)可能会影响应用的多处状态,例如双向数据绑定,很难维护调试

一个model可以更新另一个model的话,一个view更新一个model,这个model更新了另一个model,可能会引发另一个view的更新。不知道某一时刻应用到底发生了什么,因为不知道何时、为何、怎样发生的状态变化。系统不透明,很难复现bug和添加新特性

希望通过强制单向数据流来降低复杂度,提升可维护性和代码可预测性

三.核心理念
Redux用一棵不可变状态树维护整个应用的状态,无法直接改变,发生变化时,通过action和reducer创建新的对象,具体如下:

应用的状态对象没有setter,不允许直接修改

通过dispatch action来修改状态

通过reducer把action和state联系起来

由上层reducer把下层的组织起来,形成reducer树,逐层计算得到state

函数式的reducer是关键:

小(职责单一)

纯(没有副作用,不影响环境)

独立(不依赖环境,固定输入对应固定输出。容易测试,只用关注给定输入对应的返回值是否正确)

纯函数约束让一些强大的调试特性得以实现(否则状态回滚几乎是不可能的),通过DevTools精确追踪变化:

显示当前state、历史action及对应的state

跳过某些action,快速组合出bug场景,不需要手动准备

状态重置(Reset),提交(Commit),回滚(Revert)

热加载,定位reducer问题,立即修改生效

四.结构

action  与Flux一样,就是事件,带有type和data(payload)    同样手动dispatch action---store  与Flux功能一样,但全局只有1个,实现上是一颗不可变的状态树    分发action,注册listener。每个action经过层层reducer得到新state---reducer  与arr.reduce(callback, [initialValue])作用类似    reducer相当于callback,输入当前state和action,输出新state

reducer的概念相当于node中间件,或者gulp插件,每个reducer负责状态树的一小部分,把一系列reducer串联起来(把上一个reducer的输出作为当前reducer的输入),得到最终输出state

reducer每次对state的修改,都会创建一个新的state对象,旧值指向原引用,新值被创建出来

严格的单向数据流:

                  call             new stateaction --> store ------> reducers -----------> view

action也是交给顶层的所有reducer(与Flux类似),流向相应子树

store负责协调,先把action和当前state传递给reducer树,得到新state,更新当前state,再通知视图更新(React的话就是setState())

action
action负责描述发生了什么(就像新闻标题)

action与action creator分别对应传统的event和createEvent()。需要action creator是为了可移植和可测试

设计上把action creator和store分离是考虑服务端渲染,这样每个请求对应独立store,由外部做action creator和store的绑定

注意:实践中应该把创建action和dispatch action解开,在需要的场景(比如传递给子组件,希望屏蔽dispatch),Redux提供了bindActionCreators再把它们两个绑起来

另外,考虑异步场景:

action数量

一个异步操作可能需要3个action(或者1个带有3种状态的action),开始/成功/失败,对应的UI状态为显示loading/隐藏loading并显示新数据/隐藏loading并显示错误信息

更新view的时机

异步操作结束后,dispatch action修改state,更新view

不用考虑多个异步操作的时序问题,因为从action历史记录来看,顺序是固定不变的,同步还是异步过程中dispatch的不重要

与同步场景没太大区别,只是action多一些,一些中间件(redux-thunk、redux-promise等等)只是让异步控制形式上更优雅,从dispatch action角度看没有区别

reducer
负责具体的状态更新(根据action更新state,让action的描述成为事实)

相比Flux,Redux用纯函数reducer来代替event emitter:

分解与组合

通过拆分reducer来分解状态,再把reducer组合起来(combineReducers()工具函数)形成状态树,reducer组合在Redux应用里很常见(基本套路)

通常把1个reducer拆成一组相似的reducer(或者抽象出reducer factory)

单一职责

每一个reducer只负责全局状态的一部分

纯函数reducer的具体约束(与FP中的纯函数概念一致)如下:

不修改参数

只是单纯的计算,不要掺杂副作用,比如路由切换之类的其它API调用

不要调用不纯(输出不单取决于输入,还与环境有关)的方法 比如Math.random()、new Date()

另外,reducer与state密切相关,state是reducer树的计算结果,所以需要先规划整个应用的state结构,有一些非常好用的技巧:

把state分为数据状态和UI状态

UI状态可以维护在组件内部,也可以挂到状态树上,但都应该考虑区分数据状态和UI状态

(简单场景及UI状态变化可能不需要作为store的一部分,而应该在组件级来维护)

把state看做数据库

对于复杂的应用,应该把state当做数据库,存放数据时建立索引,关联数据之间通过id来引用。这样相对独立,可以减少嵌套状态(嵌套状态会让state子树越来越大,而数据表 + 关系表就不会)

Store

胶水,用来组织action和reducer,并支持listener

负责3件事:

持有state,支持读写(getState()读,dispatch(action)写)

接到action时,调度reducer

注册/解绑listener(每次状态变化时触发)

五.3个基本原则
整个应用对应一棵state树
这样很容易生成另外一份state(保留历史版本),也很容易实现redo/undo

state只读
只能通过触发action来更新state

集中变更,且以严格顺序发生(没有需要特别小心的竞争条件)

而action都是纯对象,可以记录日志、序列化,存起来以后还能回放(调试/测试)

reducer都是纯函数
输入state和action,输出新state。每次都返回新的,不维护(修改)输入的state

所以能随便调整reducer执行顺序,放电影一样的调试控制得以实现

六.react-redux
Redux与React没有任何关系,Redux作为状态管理层可以配合任何UI方案使用,例如backbone、angular、React等等

react-redux用来处理new state -> view的部分,也就是说,新state有了,怎样同步视图?

container
也有container和view的概念(与Flux相同)

container是一种特殊的组件,不含视图逻辑,与store关系紧密。从逻辑功能上看就是通过store.subscribe()读取状态树的一部分,作为props传递给下方的普通组件(view)

connect()
一个看起来很神奇的API,主要做3件事:

负责把dispatch和state数据作为props注入下方普通组件

往虚拟DOM树自动插入一些container

内置性能优化,避免不必要的更新(内置shouldComponentUpdate)

七.Redux与Flux
相同点
把Model更新逻辑单独提出来作为一层(Redux的reducer,Flux的store)

都不允许直接更新model,而要求用action描述每一个变化

(state, action) => state的基本思路是一致的

不同点
Redux是一种具体实现,而Flex是一种模式

Redux只有一个,而Flux有十好几种实现

Redux的state是1棵树

Redux把应用状态挂在1棵树上,全局只有一个store

而Flux有多个store,并把状态变更作为事件广播出去,组件通过订阅这些事件来同步当前状态

Redux没有dispatcher的概念

因为依赖纯函数,而不是事件触发器。纯函数可以随便组合,不需要额外管理顺序

在Flux里dispatcher负责把action传递给所有store

Redux假设不会手动修改state

道德约束,不允许在reducer里修改state(可以添新属性,但不允许修改现有的)

不作为强约束是考虑某些性能场景,技术上可以通过写不纯的reducer来解决

如果reducer不纯的话,依赖纯函数组合特性的强大调试功能会被破坏,所以强烈不建议这么做

不强制state用不可变的数据结构,是出于性能(不可变相关的额外处理)和灵活性(可以配合const、immutablejs等使用)考虑

八.问题与思考
1.state变化订阅机制的粒度控制是怎样的?
subscribe(listener)只能得到全局完整state,那么React setState()粒度是怎样的,怎么分子树?

手动处理。state树有任何变化都通知所有listener,listener里手动判断自己关注的那一小部分state变了没。也就是订阅机制不管分发,需要手动分发

2.react-reduct的是怎么回事?

猜一下。应该是把store挂在hostContainerInfo上了,所以要求在render root时把Provider作为顶层容器:

render(  <Provider store={store}>    <App />  </Provider>,  document.getElementById('root'))

hostContainerInfo长这样子:

function ReactDOMContainerInfo(topLevelWrapper, node) {  var info = {    _topLevelWrapper: topLevelWrapper,    _idCounter: 1,    _ownerDocument: node ? node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument : null,    _node: node,    _tag: node ? node.nodeName.toLowerCase() : null,    _namespaceURI: node ? node.namespaceURI : null  };  if ("development" !== 'production') {    info._ancestorInfo = node ? validateDOMNesting.updatedAncestorInfo(null, info._tag, null) : null;  }  return info;}

(摘自ReactDOM v15.5.4源码)

虚拟DOM树上所有组件共享hostContainerInfo,所以store在所有container里都能访问,示例代码见Usage with React

3.树的场景(无限级展开)怎么处理?
一个典型的业务场景,无限级树结构,处理技巧在于把state看做数据库(前面提到过这个技巧)

按照Redux的理念,应该把tree打平成nodes,粗粒度可以是nodeId - children,细粒度就是nodeId - node(children变成了childrenIdList,再查总id表得到children)

打平能够解决问题,比嵌套状态好维护得多,如果树组件对应一个tree对象的话(node都在tree上),对一棵大树做局部更新会很难受

P.S.3NF竟然能应用在前端,简直难以置信!

参考资料
Redux doc:非常棒的文档,读起来根本停不下来

Redux · An Introduction

更多相关文章

  1. 函数和递归
  2. java的getClass()函数
  3. 函数的学习
  4. FastAPI基础之Http状态码备忘
  5. Java 中线程池包含哪些状态?
  6. 线程包括哪些状态?状态之间是如何转变的?
  7. 状态机在移动端项目中的使用
  8. 状态机设计
  9. java多线程(3)Thread构造函数解析

随机推荐

  1. Shell脚本更改带变量的目录
  2. 初探Linux kernel之进程相关二
  3. 【Linux】CentOS7无法使用tab补全功能
  4. ARM11、OK6410_Linux、系统移植 和 驱动
  5. openfalcon - centos 5.5 + python 2.4 r
  6. RHEL6误安装RHEL7的包导致glibc被升级后
  7. 我如何在Linux和Python中监听“usb设备插
  8. Telnet套接字网关到Coldfusion事件网关,连
  9. Linux开发工具(gcc gdb make shell)——G
  10. Vue绑定内联样式问题