前景回忆
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组件的代码片断:
大家可以看到,这个时候dom的操作和js逻辑是交织在一起的。也就是C直接操作了V,V也去修改了M。
最后web应用中存在多个Model,多个View就是
咋搞,已经能想象到代码多了,全局搜出来,都不一定能看得懂
那接下来让 "单向数据流" 上来操作一下。
改变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组件串起来。
- 和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的两个最主要功能:
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进行改变
Redux
Redux相对于Flux的改进:
- 把store和Dispatcher合并,结构更加简单清晰
- 新增state角色,代表每个时间点store对应的值,对状态的管理更加明确
Redux数据流的顺序是:
View调用store.dispatch发起Action->store接受Action(action传入reducer函数,reducer函数返回一个新的state)->通知store.subscribe订阅的重新渲染函数
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检测到数据变化自动渲染)
状态管理中的异步处理
最后的最后,一个问题是为什么Vuex与Redux中 要把异步处理单独拿出来?
更改 state 的函数必须是纯函数,纯函数既是统一输入就会统一输出,没有任何副作用;如果是异步则会引入额外的副作用,导致更改后的 state 不可预测;
vuex和redux都是一种状态管理机制。然后他们会有自己的state(状态)和修改state的方法,修改state的方法涉及到同步和异步,vuex的处理方式是同步在mutation里面,异步在actions里面,然后redux的同步就是reducer,异步更多的是用户自己去通过中间件的方式去实现(redux-thunk redux-saga)
尤大的回答
区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。
事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。