Appearance
vuex
vuex是vue2中首选的状态管理工具,包括state、mutation、action 三大马车,大致是组件触发dispatch()或者commit()来修改state中的数据
redux
redux包括state,所有的state必须要通过dispatch(action) 来触发,redux内部通过事先注册号的的reducer来处理dispath(action) 的动作。reducer是一个接受action的函数,通过action来改变当前的state并返回新的state。 可以看到,vuex是在redux将action和reducer的功能简化。对于同步的场景,mutation 中定义的其实就是reducer,mutation的名字作为action。vuex中将并未将action统一化,也有将action统一管理的。比如可以用create_action 来生成标志。
js
const reducer = (state, action) => {
switch(action.type) {
case 'ADD':
return state + 1;
}
}
const store = createStore(reducer, 1);
const unSubscribe = store.subscribe(() => {
const state = store.getState()
})实现一个简易版本的redux
js
const createStore = (reducer, state) => {
let state;
const listeners = [];
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener) // 取消订阅
}
}
const dispatch = (action) => {
state = reducer(state, action);
this.listeners.forEach(listen => listen())
}
const getState = () => state
return {
dispatch,
getState,
subscribe
}
}redux 中间件
createStore第二个参数设置applyMiddleware,applyMiddleware 大致理解成核心是compose,会返回一个函数
js
export const compose = (...fns) => {
if(fns.length === 0) {
return arg => arg
}
if(fns.length === 1) {
return fns[0]
}
return fns.reduce(a, b => (...args) => a(b(...args)))
(...args) => fn1(fn2(...args))
(...args) => fn1(fn2(fn3(...args)))
}
const newFunc = compose(fn1, fn2, fn3)
newFunc(xxx)
// applyMiddle
export const thunk = (next) => (action) => {
if(typeof action === 'function') {
return action()
}
return next(action)
}
export const log = (next) => (action) => {
console.log('start')
const result = next(action)
console.end('end')
return result;
}
let dispatch = compose(thunk, log)(store.dispatch) // log(thunk(store.dispatch)) => (action)
dispatch(action) => log(action) => thunk(action) => log(action)完整版本的redux
js
export const compose = (fns) => {
if(fns.length === 0) {
return arg => arg
}
if(fns.length === 1) {
return fns[0]
}
return fns.reduce((a, b) => (...args) => a(b(...args)))
}
export const middlewareApply = (...middleWares) => {
return createStore => reducer => {
const store = createStore(reducer);
middleWares = middleWares.map(middleware => middleWares({
dispatch: (...args) => dispatch(...args),
getState: store.getState
}))
const dispatch = compose(...middleWares)(store.dispatch)
return {
...store,
dispatch
}
}
}
export const createStore = (reducer, storeEnhancer) => {
if(storeEnhancer) {
return storeEnhancer(createStore)(reducer)
}
let state
let listeners = [];
let dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener())
}
const subScribe = (listener) => {
listeners.push(listener)
return () => {
listeners = listeners.filter(l => l !== listener)
}
}
const getState = () => state
dispatch({})
return {
dispatch,
subScribe,
getState
}
}
const reducer = (state, action) => {
switch(action.type) {
case 'ADD':
return state + 1;
default:
return 0;
}
}
const thunk = ({ getState, dispatch }) => () => () => {
// next 理解为传递到下一个action处理器,其中dispatch也是一个处理器。
// 不执行next表示中间件停止
if(typeof action === 'function') {
return action()
}
return next(action)
}
const log = ({ getState, dispatch }) => next => action => {
console.log('start', getState())
const result = next(action)
console.log('end', getState());
return result;
}
const store = createStore(reducer, middlewareApply(thunk, log))
store.dispatch({
type: "ADD"
})
store.dispatch(() => {
setTimeout(() => {
store.dispatch('ADD');
}, 1000)
})vuex中的高级应用
- 支持模块,默认情况下模块是全局注册的,在多个模块中同时注册相同的名字在触发时会被同时响应,添加namespace: true后会将所有的action 和 mutation添加模块前缀,触发时需要补上前缀(在命名空间下通过root: true 注册全局)
js
export const store = new Vuex.Store({
modules: {
foo: {
namespace: true
actions: {
add() { // dispatch('foo/add', xxx)
},
del: { // dispatch('del', xxx)
root: true,
handler() {
}
}
}
},
boo: {
actions: {
del() {} // dispatch('del', xxx) 会同时触发两个模块
}
}
}
})- 支持动态注册模块 registerModule(),适合于模块数据
vuex 插件机制和执行流程
js
export const logPlugin = (pluginOptions) => { // 日志组件
return (store) => {
store.subscribe((mutation, state) => { // 订阅每次mutation操作
console.log(mutation.type, mutation.payload, state)
})
}
}
// 实现一个持久化state的插件vuex源码流程
- 注册插件 Vue.use(Vuex), 通过插件的形式在vue各个实例的beforeCreate前注入vuex.store 实例,
js
export default class Vuex {
static apply(Vue) {
Vue.mixin(beforeCreate: VueInit)
}
}
export const VueInit = () {
if(this.$parent) {
this.$store = this.$parent.$store // 如果当前是子节点,总是取父节点的,但是有个问题是如果当前节点还未挂载是没有$parent属性的,比如是new实例生成的的组件
} else {
this.$store = this.$options.store // 如果当前是根节点,取根节点传入的store
}
}执行store构造函数,初始化各个state,各个mutation监听,各个action监听。生成dispatch函数和commit函数,再执行插件初始化,最后执行devtool
- installModule 安装当前module上的state和根state绑定(如果有),通过Vue.set() 添加新的子state到上级state上
- forEachMutation, 将module中mutation绑定到根store中mutationMap上, 如果是命名空间还需要添加前缀。如果同名是推入数组
- forEachAction 和 forEachGetter 同理
- 遍历子模块继续执行installModule
初始响应式,将所有的state代理到new Vue({data: state})的实例上,getter代理到computed,这样获取state就会生成watcher进入dep,修改store中的state会触发所有的监听。
插件的应用场景有如下几个。
- 通过aop的方式处理action或者mutation的中执行错误,重写store.commit 或者 store.dispatch 函数实现
- 通过subscribe或者subscribeAction监听,实现日志或者持久化等操作,默认情况监听函数发生在action和mutation之前,通过参数prepend: true 添加到前面实现对state的再处理。
- 通过replaceState来初始化state,注意的是replace是替换根state,如果需要特殊的state,直接赋值即可
- 通过registerrModule 来注册一些业务外不关注的module
vuex 实例上需要关注的方法
state, commit, dispatch, subscribe, subscribeAction, watch, registerModule, replaceState(storagePlugin中需要用到)
