Skip to content

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中的高级应用

  1. 支持模块,默认情况下模块是全局注册的,在多个模块中同时注册相同的名字在触发时会被同时响应,添加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)  会同时触发两个模块
            }
        }
    }
})
  1. 支持动态注册模块 registerModule(),适合于模块数据

vuex 插件机制和执行流程

js
export const logPlugin = (pluginOptions) => {  // 日志组件
    return (store) => {
        store.subscribe((mutation, state) => {  // 订阅每次mutation操作
            console.log(mutation.type, mutation.payload, state)
        })
    }
}

// 实现一个持久化state的插件

vuex源码流程

  1. 注册插件 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
    }
}
  1. 执行store构造函数,初始化各个state,各个mutation监听,各个action监听。生成dispatch函数和commit函数,再执行插件初始化,最后执行devtool

    1. installModule 安装当前module上的state和根state绑定(如果有),通过Vue.set() 添加新的子state到上级state上
    2. forEachMutation, 将module中mutation绑定到根store中mutationMap上, 如果是命名空间还需要添加前缀。如果同名是推入数组
    3. forEachAction 和 forEachGetter 同理
    4. 遍历子模块继续执行installModule
  2. 初始响应式,将所有的state代理到new Vue({data: state})的实例上,getter代理到computed,这样获取state就会生成watcher进入dep,修改store中的state会触发所有的监听。

插件的应用场景有如下几个。

  1. 通过aop的方式处理action或者mutation的中执行错误,重写store.commit 或者 store.dispatch 函数实现
  2. 通过subscribe或者subscribeAction监听,实现日志或者持久化等操作,默认情况监听函数发生在action和mutation之前,通过参数prepend: true 添加到前面实现对state的再处理。
  3. 通过replaceState来初始化state,注意的是replace是替换根state,如果需要特殊的state,直接赋值即可
  4. 通过registerrModule 来注册一些业务外不关注的module

vuex 实例上需要关注的方法

state, commit, dispatch, subscribe, subscribeAction, watch, registerModule, replaceState(storagePlugin中需要用到)

pinia