5. Vue 周边生态

您所在的位置:网站首页 vuex4用法 5. Vue 周边生态

5. Vue 周边生态

2023-04-21 14:09| 来源: 网络整理| 查看: 265

Vuex 3.x 源码解析Store 类核心原理State 的响应式原理State 修改方法commit(mutation) 和 dispatch(action) 的原理devtools 原理

https://tech.meituan.com/2017/04/27/vuex-code-analysis.html

Vuex 3.x 源码解析

index.js 对外暴漏了可以访问的方法:

install:vuex 的安装 pluginStore 类mapState, mapMutations, mapGetters, mapAction ```javascript import { Store, install } from ‘./store’ import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from ‘./helpers’ import createLogger from ‘./plugins/logger’

export default { Store, install, version: ‘VERSION‘, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers, createLogger }

export { Store, install, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers, createLogger }

Vuex 一共有三个核心类:- Store- Module- ModuleCollectionStore 类主要有以下几个方法:- commit- dispatch- subscribe- subscribeAction- watch- replaceState- registerModule- unregisterModule- hasModule- hotUpdate- _withCommitStore Utils 核心方法:- genericSubscribe- resetStore- resetStoreVM(resetStoreState)- installModule- makeLocalContext- makeLocalGetters- registerMutation- registerAction- registerGetter- enableStrictMode- getNestedState- unifyObjectStyle```javascriptimport applyMixin from './mixin'import devtoolPlugin from './plugins/devtool'import ModuleCollection from './module/module-collection'import { forEachValue, isObject, isPromise, assert, partial } from './util'let Vue // bind on installexport class Store { constructor (options = {}) { // Auto install if it is not done yet and `window` has `Vue`. // To allow users to avoid auto-installation in some cases, // this code should be placed here. See #731 if (!Vue && typeof window !== 'undefined' && window.Vue) { install(window.Vue) } if (__DEV__) { assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`) assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`) assert(this instanceof Store, `store must be called with the new operator.`) } const { plugins = [], strict = false } = options // store internal state this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue() this._makeLocalGettersCache = Object.create(null) // bind commit and dispatch to self const store = this const { dispatch, commit } = this this.dispatch = function boundDispatch (type, payload) { return dispatch.call(store, type, payload) } this.commit = function boundCommit (type, payload, options) { return commit.call(store, type, payload, options) } // strict mode this.strict = strict const state = this._modules.root.state // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreVM(this, state) // apply plugins plugins.forEach(plugin => plugin(this)) const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools if (useDevtools) { devtoolPlugin(this) } } get state () { return this._vm._data.$$state } set state (v) { if (__DEV__) { assert(false, `use store.replaceState() to explicit replace store state.`) } } commit (_type, _payload, _options) { // check object-style commit const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } const entry = this._mutations[type] if (!entry) { if (__DEV__) { console.error(`[vuex] unknown mutation type: ${type}`) } return } this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) this._subscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .forEach(sub => sub(mutation, this.state)) if ( __DEV__ && options && options.silent ) { console.warn( `[vuex] mutation type: ${type}. Silent option has been removed. ` + 'Use the filter functionality in the vue-devtools' ) } } dispatch (_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload) const action = { type, payload } const entry = this._actions[type] if (!entry) { if (__DEV__) { console.error(`[vuex] unknown action type: ${type}`) } return } try { this._actionSubscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .filter(sub => sub.before) .forEach(sub => sub.before(action, this.state)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in before action subscribers: `) console.error(e) } } const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) return new Promise((resolve, reject) => { result.then(res => { try { this._actionSubscribers .filter(sub => sub.after) .forEach(sub => sub.after(action, this.state)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in after action subscribers: `) console.error(e) } } resolve(res) }, error => { try { this._actionSubscribers .filter(sub => sub.error) .forEach(sub => sub.error(action, this.state, error)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in error action subscribers: `) console.error(e) } } reject(error) }) }) } subscribe (fn, options) { return genericSubscribe(fn, this._subscribers, options) } subscribeAction (fn, options) { const subs = typeof fn === 'function' ? { before: fn } : fn return genericSubscribe(subs, this._actionSubscribers, options) } watch (getter, cb, options) { if (__DEV__) { assert(typeof getter === 'function', `store.watch only accepts a function.`) } return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options) } replaceState (state) { this._withCommit(() => { this._vm._data.$$state = state }) } registerModule (path, rawModule, options = {}) { if (typeof path === 'string') path = [path] if (__DEV__) { assert(Array.isArray(path), `module path must be a string or an Array.`) assert(path.length > 0, 'cannot register the root module by using registerModule.') } this._modules.register(path, rawModule) installModule(this, this.state, path, this._modules.get(path), options.preserveState) // reset store to update getters... resetStoreVM(this, this.state) } unregisterModule (path) { if (typeof path === 'string') path = [path] if (__DEV__) { assert(Array.isArray(path), `module path must be a string or an Array.`) } this._modules.unregister(path) this._withCommit(() => { const parentState = getNestedState(this.state, path.slice(0, -1)) Vue.delete(parentState, path[path.length - 1]) }) resetStore(this) } hasModule (path) { if (typeof path === 'string') path = [path] if (__DEV__) { assert(Array.isArray(path), `module path must be a string or an Array.`) } return this._modules.isRegistered(path) } hotUpdate (newOptions) { this._modules.update(newOptions) resetStore(this, true) } _withCommit (fn) { const committing = this._committing this._committing = true fn() this._committing = committing }}function genericSubscribe (fn, subs, options) { if (subs.indexOf(fn) < 0) { options && options.prepend ? subs.unshift(fn) : subs.push(fn) } return () => { const i = subs.indexOf(fn) if (i > -1) { subs.splice(i, 1) } }}function resetStore (store, hot) { store._actions = Object.create(null) store._mutations = Object.create(null) store._wrappedGetters = Object.create(null) store._modulesNamespaceMap = Object.create(null) const state = store.state // init all modules installModule(store, state, [], store._modules.root, true) // reset vm resetStoreVM(store, state, hot)}function resetStoreVM (store, state, hot) { const oldVm = store._vm // bind store public getters store.getters = {} // reset local getters cache store._makeLocalGettersCache = Object.create(null) const wrappedGetters = store._wrappedGetters const computed = {} forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism // direct inline function use will lead to closure preserving oldVm. // using partial to return function with only arguments preserved in closure environment. computed[key] = partial(fn, store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }) }) // use a Vue instance to store the state tree // suppress warnings just in case the user has added // some funky global mixins const silent = Vue.config.silent Vue.config.silent = true store._vm = new Vue({ data: { $$state: state }, computed }) Vue.config.silent = silent // enable strict mode for new vm if (store.strict) { enableStrictMode(store) } if (oldVm) { if (hot) { // dispatch changes in all subscribed watchers // to force getter re-evaluation for hot reloading. store._withCommit(() => { oldVm._data.$$state = null }) } Vue.nextTick(() => oldVm.$destroy()) }}function installModule (store, rootState, path, module, hot) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { if (store._modulesNamespaceMap[namespace] && __DEV__) { console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`) } store._modulesNamespaceMap[namespace] = module } // set state if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { if (__DEV__) { if (moduleName in parentState) { console.warn( `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"` ) } } Vue.set(parentState, moduleName, module.state) }) } const local = module.context = makeLocalContext(store, namespace, path) module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) }) module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) })}/** * make localized dispatch, commit, getters and state * if there is no namespace, just use root ones */function makeLocalContext (store, namespace, path) { const noNamespace = namespace === '' const local = { dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type if (__DEV__ && !store._actions[type]) { console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`) return } } return store.dispatch(type, payload) }, commit: noNamespace ? store.commit : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type if (__DEV__ && !store._mutations[type]) { console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`) return } } store.commit(type, payload, options) } } // getters and state object must be gotten lazily // because they will be changed by vm update Object.defineProperties(local, { getters: { get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace) }, state: { get: () => getNestedState(store.state, path) } }) return local}function makeLocalGetters (store, namespace) { if (!store._makeLocalGettersCache[namespace]) { const gettersProxy = {} const splitPos = namespace.length Object.keys(store.getters).forEach(type => { // skip if the target getter is not match this namespace if (type.slice(0, splitPos) !== namespace) return // extract local getter type const localType = type.slice(splitPos) // Add a port to the getters proxy. // Define as getter property because // we do not want to evaluate the getters in this time. Object.defineProperty(gettersProxy, localType, { get: () => store.getters[type], enumerable: true }) }) store._makeLocalGettersCache[namespace] = gettersProxy } return store._makeLocalGettersCache[namespace]}function registerMutation (store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []) entry.push(function wrappedMutationHandler (payload) { handler.call(store, local.state, payload) })}function registerAction (store, type, handler, local) { const entry = store._actions[type] || (store._actions[type] = []) entry.push(function wrappedActionHandler (payload) { let res = handler.call(store, { dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootState: store.state }, payload) if (!isPromise(res)) { res = Promise.resolve(res) } if (store._devtoolHook) { return res.catch(err => { store._devtoolHook.emit('vuex:error', err) throw err }) } else { return res } })}function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { if (__DEV__) { console.error(`[vuex] duplicate getter key: ${type}`) } return } store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) }}function enableStrictMode (store) { store._vm.$watch(function () { return this._data.$$state }, () => { if (__DEV__) { assert(store._committing, `do not mutate vuex store state outside mutation handlers.`) } }, { deep: true, sync: true })}function getNestedState (state, path) { return path.reduce((state, key) => state[key], state)}function unifyObjectStyle (type, payload, options) { if (isObject(type) && type.type) { options = payload payload = type type = type.type } if (__DEV__) { assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`) } return { type, payload, options }}export function install (_Vue) { if (Vue && _Vue === Vue) { if (__DEV__) { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } Vue = _Vue applyMixin(Vue)}

Store 类核心原理

初始化 store 的内部状态

绑定 dispatch 和 commit

const { dispatch, commit } = thisthis.dispatch = function boundDispatch (type, payload) {return dispatch.call(store, type, payload)}this.commit = function boundCommit (type, payload, options) {return commit.call(store, type, payload, options)}

初始化 root module:installModule(this, state, [], this._modules.root)

初始化 store state,它负责状态的响应式:resetStoreState,vuex3 是 resetStoreVM State 的响应式原理其实就是把 state 挂载到 vue 上。

State 修改方法

_withCommit是一个代理方法,所有触发mutation的进行state修改的操作都经过它,由此来统一管理监控state状态的修改。实现代码如下。

_withCommit (fn) { // 保存之前的提交状态 const committing = this._committing // 进行本次提交,若不设置为true,直接修改state,strict模式下,Vuex将会产生非法修改state的警告 this._committing = true // 执行state的修改操作 fn() // 修改完成,还原本次修改之前的状态 this._committing = committing}

commit(mutation) 和 dispatch(action) 的原理

commit 和 dispatch 原理类似,都是通过触发 entry(commit 是 mutation[type],dispatch 是 action[type]),订阅者遍历执行。

this._subscribers.slice().forEach(sub => sub(mutation, this.state)this._actionSubscribers.filter(sub => sub.before).forEach(sub => sub.before(action, this.state))this._actionSubscribers.filter(sub => sub.after).forEach(sub => sub.after(action, this.state))

commit 通过 _withCommit来改变 state,dispatch 通过下述代码拿到结果:

const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload)

commit 源码如下:

commit (_type, _payload, _options) { // check object-style commit const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } const entry = this._mutations[type] if (!entry) { if (__DEV__) { console.error(`[vuex] unknown mutation type: ${type}`) } return } this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) this._subscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .forEach(sub => sub(mutation, this.state)) if ( __DEV__ && options && options.silent ) { console.warn( `[vuex] mutation type: ${type}. Silent option has been removed. ` + 'Use the filter functionality in the vue-devtools' ) } }

dispatch 源码如下:

dispatch (_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload) const action = { type, payload } const entry = this._actions[type] if (!entry) { if (__DEV__) { console.error(`[vuex] unknown action type: ${type}`) } return } try { this._actionSubscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .filter(sub => sub.before) .forEach(sub => sub.before(action, this.state)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in before action subscribers: `) console.error(e) } } const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) return new Promise((resolve, reject) => { result.then(res => { try { this._actionSubscribers .filter(sub => sub.after) .forEach(sub => sub.after(action, this.state)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in after action subscribers: `) console.error(e) } } resolve(res) }, error => { try { this._actionSubscribers .filter(sub => sub.error) .forEach(sub => sub.error(action, this.state, error)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in error action subscribers: `) console.error(e) } } reject(error) }) }) }

devtools 原理

核心的 travel 原理如下:

通过 devtoolHook.on 方法注册事件:devtoolHook.on('vuex:travel-to-state')调用 store 的 replaceState 完成旅行穿梭 ```javascript const target = typeof window !== ‘undefined’ ? window : typeof global !== ‘undefined’ ? global : {} const devtoolHook = target.VUE_DEVTOOLS_GLOBAL_HOOK

export default function devtoolPlugin (store) { if (!devtoolHook) return

store._devtoolHook = devtoolHook

devtoolHook.emit(‘vuex:init’, store)

devtoolHook.on(‘vuex:travel-to-state’, targetState => { store.replaceState(targetState) })

store.subscribe((mutation, state) => { devtoolHook.emit(‘vuex:mutation’, mutation, state) }, { prepend: true })

store.subscribeAction((action, state) => { devtoolHook.emit(‘vuex:action’, action, state) }, { prepend: true }) }

## Vuex 4.x 源码解析相比于 vuex 3.x,vuex4 将操作 store 的一个 utils function 单独抽离了出来,并且没有提供 `install` 方法来安装 store,而是通过 `createStore` 函数来创建 Store,`useStore` hooks 来 inject(vue3 的 api)来安装 store```javascriptimport { watch } from 'vue'import { storeKey } from './injectKey'import { addDevtools } from './plugins/devtool'import ModuleCollection from './module/module-collection'import { assert } from './util'import { genericSubscribe, getNestedState, installModule, resetStore, resetStoreState, unifyObjectStyle} from './store-util'export function createStore (options) { return new Store(options)}export class Store { constructor (options = {}) { if (__DEV__) { assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`) assert(this instanceof Store, `store must be called with the new operator.`) } const { plugins = [], strict = false, devtools } = options // store internal state this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._makeLocalGettersCache = Object.create(null) // EffectScope instance. when registering new getters, we wrap them inside // EffectScope so that getters (computed) would not be destroyed on // component unmount. this._scope = null this._devtools = devtools // bind commit and dispatch to self const store = this const { dispatch, commit } = this this.dispatch = function boundDispatch (type, payload) { return dispatch.call(store, type, payload) } this.commit = function boundCommit (type, payload, options) { return commit.call(store, type, payload, options) } // strict mode this.strict = strict const state = this._modules.root.state // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root) // initialize the store state, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreState(this, state) // apply plugins plugins.forEach(plugin => plugin(this)) } install (app, injectKey) { app.provide(injectKey || storeKey, this) app.config.globalProperties.$store = this const useDevtools = this._devtools !== undefined ? this._devtools : __DEV__ || __VUE_PROD_DEVTOOLS__ if (useDevtools) { addDevtools(app, this) } } get state () { return this._state.data } set state (v) { if (__DEV__) { assert(false, `use store.replaceState() to explicit replace store state.`) } } commit (_type, _payload, _options) { // check object-style commit const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } const entry = this._mutations[type] if (!entry) { if (__DEV__) { console.error(`[vuex] unknown mutation type: ${type}`) } return } this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) this._subscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .forEach(sub => sub(mutation, this.state)) if ( __DEV__ && options && options.silent ) { console.warn( `[vuex] mutation type: ${type}. Silent option has been removed. ` + 'Use the filter functionality in the vue-devtools' ) } } dispatch (_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload) const action = { type, payload } const entry = this._actions[type] if (!entry) { if (__DEV__) { console.error(`[vuex] unknown action type: ${type}`) } return } try { this._actionSubscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .filter(sub => sub.before) .forEach(sub => sub.before(action, this.state)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in before action subscribers: `) console.error(e) } } const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) return new Promise((resolve, reject) => { result.then(res => { try { this._actionSubscribers .filter(sub => sub.after) .forEach(sub => sub.after(action, this.state)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in after action subscribers: `) console.error(e) } } resolve(res) }, error => { try { this._actionSubscribers .filter(sub => sub.error) .forEach(sub => sub.error(action, this.state, error)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in error action subscribers: `) console.error(e) } } reject(error) }) }) } subscribe (fn, options) { return genericSubscribe(fn, this._subscribers, options) } subscribeAction (fn, options) { const subs = typeof fn === 'function' ? { before: fn } : fn return genericSubscribe(subs, this._actionSubscribers, options) } watch (getter, cb, options) { if (__DEV__) { assert(typeof getter === 'function', `store.watch only accepts a function.`) } return watch(() => getter(this.state, this.getters), cb, Object.assign({}, options)) } replaceState (state) { this._withCommit(() => { this._state.data = state }) } registerModule (path, rawModule, options = {}) { if (typeof path === 'string') path = [path] if (__DEV__) { assert(Array.isArray(path), `module path must be a string or an Array.`) assert(path.length > 0, 'cannot register the root module by using registerModule.') } this._modules.register(path, rawModule) installModule(this, this.state, path, this._modules.get(path), options.preserveState) // reset store to update getters... resetStoreState(this, this.state) } unregisterModule (path) { if (typeof path === 'string') path = [path] if (__DEV__) { assert(Array.isArray(path), `module path must be a string or an Array.`) } this._modules.unregister(path) this._withCommit(() => { const parentState = getNestedState(this.state, path.slice(0, -1)) delete parentState[path[path.length - 1]] }) resetStore(this) } hasModule (path) { if (typeof path === 'string') path = [path] if (__DEV__) { assert(Array.isArray(path), `module path must be a string or an Array.`) } return this._modules.isRegistered(path) } hotUpdate (newOptions) { this._modules.update(newOptions) resetStore(this, true) } _withCommit (fn) { const committing = this._committing this._committing = true fn() this._committing = committing }}


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3