一、什么是Redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。源于Facebook的flux架构。是一个使用叫做“action”的事件来管理和更新应用状态的模式和工具库 它以集中式Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。利用React中Context
(上下文)在组件之间共享状态的特性实现。
二、核心概念
1. state
整个应用的全局state ,存储在唯一一个store中, store通过createStore函数生成
示例:
//store/index.js
import {createStore} from 'redux';
//引入Reducer
import Reducer from './reducers';
const configureStore = (initialState) => {
const store = createStore(Reducer, initialState);
return store;
}
export default configureStore({
addCount: {
count: 0,
},
})
2. reducer
reducer
定义 state
改变的规则, 返回新的state
给store
,只有在reducer
中才能改变state
。reducer必须是纯函数,可以有多个,通过combineReducers 函数合成一个根Reducer。
示例:
const initState = {
count: 0,
};
const addCount = (state = initState, action) => {
switch (action.type) {
case 'ADD_COUNT':
return {
...state,
...action.count,
};
default:
return state;
}
}
export default combineReducers({
addCount
});
3. action
用来描述当前发生的操作,定义操作类型与参数,并传递给Reducer
。
Action
通过 Store
的 Dispatch
方法传递给 Store
,Store
接收到 Action
,连同之前的 State
一起传给 Reducer
示例:
const addCount = (count) => {
return {
type: 'ADD_COUNT',
count
};
};
4. Middleware中间件
在 Redux 中,同步的表现就是:Action 发出以后,Reducer 立即算出 State。那么异步的表现就是:Action 发出以后,过一段时间再执行 Reducer。那怎么才能 Reducer 在异步操作结束后自动执行呢?Redux 引入了中间件 Middleware
的概念。
实际上我们说的中间件指的是对 Dispatch 方法的封装。常用中间件:
-
redux-thunk 中间件 重写store.dispatch,解决异步操作
-
redux-promise 中间件 使得store.dispatch方法可以接受 Promise 对象作为参数
-
redux-logger中间件 日志中间件
示例代码:
//store/index.js
import {createStore, applyMiddleware} from 'redux';
//引入Reducer
import Reducer from './reducers';
//引入中间件
import logger from 'redux-logger';
const configureStore = (initialState) => {
// 通过 applyMiddleware来集成中间件
const store = createStore(Reducer, initialState, applyMiddleware(logger));
return store;
}
export default configureStore({
addCount: {
count: 0,
},
})
5. connect
connect是view与redux之间的桥梁。那他们是如何关联的呢,请看示例:
import React, {Component} from 'react';
import {addCount} from './store/actions/index';
import {connect} from 'react-redux';
class App extends Component {
constructor(props) {
super(props)
}
addCount = () => {
this.props.dispatch(addCount({count: this.props.count + 1}));
}
render() {
return (
<div className='App'>
<div>当前计数:{this.props.count}</div>
<div className='btn' onClick={this.addCount}>增加</div>
</div>
)
}
}
//添加store中的state
const mapStateToProps = (store) => {
return {
count: store.addCount.count,
};
};
//添加dispatch方法
const mapDispatchToProps = (dispatch) => {
return {
dispatch,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
在view中通过redux提供的connect方法将 store中的state与dispatch方法传入view的props中,以下是connect函数的核心代码
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
// 从祖先Component处获得store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = { storeState: null }
// 对stateProps、dispatchProps、parentProps进行合并
this.updateState()
}
shouldComponentUpdate(nextProps, nextState) {
// 进行判断,当数据发生改变时,Component重新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 改变Component的state
this.store.subscribe(() = {
this.setState({
storeState: this.store.getState()
})
})
}
render() {
// 生成包裹组件Connect
return (
<WrappedComponent {...this.nextState} />
)
}
}
Connect.contextTypes = {
store: storeShape
}
return Connect;
}
}
6.数据流
此时我们看一下Redux事件流
总结一下:Store通过connect将state、dispatch传给View, view调用dispatch发送Action,中间件处理action以及异步任务,处理完之后将结果放到action中并发送给Store,Store将action、previousState发送给Reducer来更改对应的State,并将处理之后的state返回给Store,view监听到state的变化之后从而触发Render。
三、技术演进
1. dva
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,也可以理解为一个轻量级的应用框架。
redux-saga是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
核心API
-
State 全局状态
-
Action
Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。
示例:
dispatch({ type: 'add', });
-
dispatch函数
一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式
-
Reducer
与Redux的reducer一样,描述如何改变数据的,必须是”纯函数”【一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数】
示例:
// 纯函数 const foo = (a,b) => a + b; const bar = (obj, b) => obj.x + b; //以下不是纯函数 const a = 1; const foo = (b) => a + b; const foo = (obj, b) => { obj.x = 2; return obj.x + b; }
-
Effect
Effect被称为副作用,在我们的应用中,最常见的就是异步操作。dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。
示例:
effects: { *fetch({ payload }, { call, put, select }) { let count = yield select(state => state.example.count); count ++; console.log(count, 'count---') yield put({ type: 'save', payload: {count: count}}); }, },
-
Subscription
Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
示例:
subscriptions: { setup({dispatch, history}) { history.listen(({pathname}) => { if (pathname === '/users) { dispatch({ type: 'users/fetch' }); } }) }, }
2. Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex借鉴了Redux的思想,并且针对web应用的开发模式和VUE框架做了优化。
核心概念
-
State 单一状态树,包含了全部的应用层级状态
-
Getters 类似于computed,用于从state中派生出一些状态
-
Mutations
更改Vuex 的 store 中的状态的唯一方法是提交 mutation。Mutation需遵守Vue的响应规则;Mutation必须是同步函数
-
Actions
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。
-
Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加
namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。 -
plugins
Vuex 插件就是一个函数,它接收 store 作为唯一参数:
插件可用来同步数据源到store,监控state变化,生成state快照等,内置
Logger
插件示例:
import createLogger from 'vuex/dist/logger' Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production' export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules: {}, plugins: debug ? [createLogger()] : [] })
3. Vuex、dva事件流
对比流程图我们发现,Vuex与dva的数据流向是极为相似的,区别只是api的命名以及实现方式不同,Reducer变成了Mutation,effect变成了action。
四、什么情况下应该用状态管理
1. 当遇到如下问题时,建议开始使用 :
- 你有很多数据随时间而变化
- 你希望状态有一个唯一确定的来源(single source of truth)
- 你发现将所有状态放在顶层组件中管理已不可维护
例如: 用户信息、位置信息等,全局唯一且共用的属性
2. 当页面组件嵌套达到3层及以上时
父子组件嵌套达到3层时,数据传递时需要层层处理,容易出现漏洞,维护成本高。
3. 当相同页面会在路由中重复出现时不建议使用或谨慎使用
比如商品详情页,没有状态的隔离,会导致页面数据错乱。
如果逻辑实在复杂着实需要使用状态管理, 在Vuex中推荐使用vuex的模块重用(仅 2.3.0+ 支持)
即使用module的store.registerModule
、store.unregisterModule(moduleName)
进行动态注册
这是module的state与vue组件内的data
有同样的问题,所以需要使用函数来声明模块的状态
const MyReusableModule = {
state: () => ({
foo: 'bar'
}),
// mutation、action 和 getter 等等...
}
示例:
// src/store/dynamicModule/dynamicGoods.js
import store from '../index'
import goods from '../moduleGoods'
export default {
install (key) {
store.registerModule(key, goods)
},
uninstall (key) {
store.unregisterModule(key)
}
}
// Vue文件
created () {
// 用路由名称 + query + 时间戳 创建唯一routerKey进行隔离
this.routerKey = `${this.$route.name}${this.$route.query.productId || ''}${new Date().getTime()}`
console.log('created-key', this.routerKey)
storeGoods.install(this.routerKey)
this.$store.dispatch(`${this.routerKey}/changeGoodsId`, this.$route.query.productId)
},
computed: {
getNum () {
// 通过唯一routerKey获取对应module数据
return this.$store.state[this.routerKey].num
},
},
在dva中原理类似,可直接将model中的state改为Map
,利用页面路由参数创建唯一Key,作为Map的key来进行存储数据,达到隔离数据的效果。
Vuex使用误区
1.vuex中action里可以直接操作state且会生效
changeCount ({ commit, state }) {
state.count++
// commit('setCount', state.count + 1)
}
虽然可以生效,但是不建议这么使用,不利于追踪state的改变,state必须在Mutations中进行改变。