React 之 react-redux 的前世今生

356 阅读6分钟

前景回忆

React下setState的同步异步渲染问题

问题1:setState是异步还是同步更新属性?useState是异步还是同步更新属性?

问题2:setState 什么时候会是同步渲染页面,什么时候会是异步渲染页面

一个demo:codesandbox.io/s/9oo7w?fil…

一个结论:

通过js的事件绑定程序 addEventListener 和使用setTimeout/setInterval 等 React 无法掌控的 APIs情况下,setState是同步更新state

hook下,通过useEffect获得最新的state状态

进入正题,讲讲今天的主角 react-redux

不知从何说起,那就让我们从单向数据流说起好了 -。-!!!

这一说单向数据流,又突然想回头再巩固一下MVC和MVVM。那就从 MVC和MVVM 简单说起吧。囧~~~···

那MVC是啥呢?一个分层开发概念。 既然是个概念那反正大家都可以往里面套么, 前端怎么把自己套进去呢?

M:Modal,数据源(State)
V:View, 视图 (html,css)
C: controller (js)

backbonejs 是一个广受欢迎的轻量级MVC前端框架,我们先来看一个 Backbone.js Todo Example,下面是其View组件的代码片断:

image.png

大家可以看到,这个时候dom的操作和js逻辑是交织在一起的。也就是C直接操作了V,V也去修改了M。

image.png

最后web应用中存在多个Model,多个View就是

image.png

咋搞,已经能想象到代码多了,全局搜出来,都不一定能看得懂

那接下来让 "单向数据流" 上来操作一下。

image.png

改变state,让view自动更新

那先认识一下这3个东西:

Flux:Flux是一个系统架构,用于推进应用中的数据单向流动

redux:Redux在Flux基础上强调三个基本原则

1:唯一数据源 2:保持状态只读 3:数据改变只能通过纯函数完成

  • 定义action类型
  • 创建action构造函数
  • 用createStore创建store:包含更新状态reducer、初始状态、(StoreEnhancer可选)
  • 创建reducer:包含state和ation参数
  • view部分初始化状态:通过store.getStore()获取状态
  • 通过subscribe和unsubscribe同步状态
  • 通过store.dispatch派发action,从而改变store状态
redux = Flux + reducer

store内部状态流转是顺利了,那接下来还要把store和react组件串起来。

image.png

  • 和Redux Store打交道,读取Store的状态,用于初始化组件的状态,同时还要监听Store的状态改变;当Store状态发生变化时,需要更新组件状态,从而驱动组件重新渲染;当需要更新Store状态时,就要派发action对象;
  • 根据当前props和state,渲染出用户界面

如果发现一个组件做的事情太多了,就可以把这个组件拆分成多个组件,让每个组件依然只专注做一件事:

  • 负责和Redux Store打交道的组件,处于外层,被称为容器组件(Container Component)
  • 负责渲染界面的组件,处于内层,叫做展示组件(PresentationalComponent)
  • 组件中直接导入Store是非常不利于组件复用的
  • 不让组件直接导入Store,那就只能让组件的上层组件把Store传递下来了。然而props存在缺陷,即所有的组件都要帮助传递这个props。React提供了一个叫Context的功能,能够完美地解决这个问题

react中的context是什么呢?官方文档给出:

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

Context 能让你将这些数据向组件树下所有的组件进行“广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据。

官方地址:react.docschina.org/docs/contex…

React-Redux:

那么,使用Redux不是不可以,只不过用起来着实麻烦,需要一直订阅store里的数据,每次都需要写一遍,为了方便,Redux的作者封装了React专用的库react-redux;

react-redux的两个最主要功能:

image.png

Provider:提供包含store的context

Provider模块的功能并不复杂,主要分为以下两点:
  • 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
  • 接收Redux的store作为props,通过context对象传递给子孙组件上的connect

connect:连接容器组件和傻瓜组件;

 connect模块才是真正连接了React和Redux
import {Provider} from 'react-redux'
import {connect} from 'react-redux'; 
...
export default connect(mapStateToProps,mapDispatchToProps)(Counter)

完整DEMO

//Counter.js 
import * as Actions from '../Actions'
import {connect} from 'react-redux';

//把Store上的状态转化为内层傻瓜组件的prop
function mapStateToProps(state,ownProps){ 
    return { value:state[ownProps.caption] }
} 

//把内层傻瓜组件中的用户动作转化为派送给Store的动作 function mapDispatchToProps(dispatch,ownProps){ 
    return{
        onIncrement:()=>{
            dispatch(Actions.increment(ownProps.caption))
        },
        onDecrement:()=>{
            dispatch(Actions.decrement(ownProps.caption)) 
        } 
      } 
} 

//导出容器组件
export default connect(mapStateToProps,mapDispatchToProps)(Counter) 

//傻瓜组件
function Counter(props){
    const {caption,onIncrement,onDecrement,value}=props
    return( <div> <input type="button" onClick={onIncrement} value="+"/>
    <input type="button" onClick={onDecrement} value="-"/> 
    <span>{caption} 
    count:{value}</span>
    </div> )
}
//Summary.js
import {connect} from 'react-redux' 
//把Store上的状态转化为内层傻瓜组件的prop 
function mapStateToProps(state) {
    let sum = 0; 
    for (const key in state) {
        if (state.hasOwnProperty(key)) { sum += state[key]; }
    } 
        return {value: sum};
} 

function Summary({value}) { 
        return ( <div>Total Count: {value}</div> ); 
} 

export default connect(mapStateToProps)(Summary);

//index.js 
import store from './Store' 
import {Provider} from 'react-redux' 

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

数据管理工具

Flux

Flux数据流的顺序是: View发起Action->Action传递到Dispatcher->Dispatcher将通知Store->Store的状态改变通知View进行改变

image.png

ps:基于Flux架构思想写的一个小demo

Redux

Redux相对于Flux的改进:

  • 把store和Dispatcher合并,结构更加简单清晰
  • 新增state角色,代表每个时间点store对应的值,对状态的管理更加明确

Redux数据流的顺序是:
View调用store.dispatch发起Action->store接受Action(action传入reducer函数,reducer函数返回一个新的state)->通知store.subscribe订阅的重新渲染函数

image.png

ps:阮一峰老师的Redux+React小demo

Vuex

Vuex是专门为Vue设计的状态管理框架,
同样基于Flux架构,并吸收了Redux的优点
Vuex相对于Redux的不同点有:

  • 改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,
  • 无需switch,只需在对应的mutation函数里改变state值即可
  • 由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可

Vuex数据流的顺序是:
View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)

image.png

ps:vuex官方Vue+vuex小demo

状态管理中的异步处理

最后的最后,一个问题是为什么Vuex与Redux中 要把异步处理单独拿出来?

更改 state 的函数必须是纯函数,纯函数既是统一输入就会统一输出,没有任何副作用;如果是异步则会引入额外的副作用,导致更改后的 state 不可预测;

vuex和redux都是一种状态管理机制。然后他们会有自己的state(状态)和修改state的方法,修改state的方法涉及到同步和异步,vuex的处理方式是同步在mutation里面,异步在actions里面,然后redux的同步就是reducer,异步更多的是用户自己去通过中间件的方式去实现(redux-thunk redux-saga)

尤大的回答

区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。

事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。