vuexтак какVueНеотъемлемая часть всего семейного ведра, изучение и понимание его исходного кода может не только изучить превосходные идеи автора и навыки написания кода, но и помочь нам писать более качественный и стандартизированный код в процессе разработки.
Исходная версия 3.1.2, старайтесь не использовать исходный код напрямую во время отладки.console.log, поскольку иногда его вывод не соответствует ожидаемым данным, рекомендуется использоватьdebuggerОтладка и чтение исходного кода В следующей статье некоторая обработка совместимости и надежности в исходном коде будет соответствующим образом проигнорирована, и будет виден только основной процесс.
use
существуетvueКогда плагин используется вVue.use(Vuex)Плагин будет обработан, этот процесс пройдетmixinСпасательные крючки в различных компонентахbeforeCreateувеличение для каждого экземпляра в$storeАтрибуты
install
существуетvueпроект, использоватьvuexДля управления данными первое, что нужно сделать, этоvuexимпорт иVue.use(Vuex)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
в исполненииVue.use(Vuex), это вызоветvuexметоды, представленные вinstallинициализация и будетVueПередано как параметр, всеvueПлагины предоставляютinstallметод, используемый для инициализации некоторых операций, метод находится в/src/store.jsподвергается в
let Vue // bind on install
export function install (_Vue) {
// 容错判断
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue // 只初始化赋值一次--单例模式
applyMixin(Vue)
}
первый вstoreопределить переменную вVue, принятьVueпример
installВ функции он сначала определит, была ли она вызванаVue.use(Vuex), а затем позвонитеapplyMixinметод для инициализации некоторых операций
Суммировать:installМетод только сделали отказоустойчивым, а потом вызовapplyMixin,Vueназначать
applyMixin
applyMixinметод в/src/mixinвыставлены в$storeобъект
export default function (Vue) {
// 获取当前的vue版本号
const version = Number(Vue.version.split('.')[0])
// 若是2以上的vue版本,直接通过mixin进行挂载$store
if (version >= 2) {
// 在每个实例beforeCreate的时候进行挂载$store
Vue.mixin({ beforeCreate: vuexInit })
} else {
// vue 1.x版本处理 省略...
}
function vuexInit () {
// 1. 获取每个组件实例的选项
const options = this.$options
// 2. 检测options.store是否存在
if (options.store) {
// 下面详细说明
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 检测当前组件实例的父组件是够存在,并且其父组件存在$store
// 存在,则为当前组件实例挂载$store属性
this.$store = options.parent.$store
}
}
}
весьmixinТрудность заключается в понимании файлаthis.$store = typeof options.store === 'function' ? options.store() : options.storeчто ты сделал
В использованииvuex, будуstoreМонтируется на корневой компонент
import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(Counter)
})
в первый звонокvuexInitфункция,options.storeЭто корень вариантовstore, поэтому он определит, является ли его типfunction, если да, выполните функцию и присвойте результат корневому экземпляру$store, в противном случае назначьте напрямую.
Резюме: весьmixinЧто делает файл, так это использоватьmixinЖизненные крючки в отдельных экземплярахbeforeCreateдобавить к нему атрибуты$storeи присвойте ему значение, гарантируя, что его можно будет напрямую передать в каждом экземпляреthis.$storeПолучите данные и поведение.
Module
moduleОсновная функция модуля: определить нашиstoreПо определенным правилам он преобразуется в древовидную структуру и инстанцируетсяStoreПри выполнении он назначит полученную древовидную структуруthis._modules, и последующие операции будут выполняться на основе этого дерева.
древовидная структура
Сначала мыvuexОпределите некоторые состояния и модули и посмотрите, что представляет собой трансформированная древовидная структура.
const state = {
count: 0
}
const getters = {
}
const mutations = {
}
const actions = {
}
const modules = {
moduleA:{
state: {
a: 'module a'
},
modules: {
moduleB:{
state: {
b: 'module b'
}
}
}
},
moduleC: {
state: {
c: 'module c'
}
}
}
export default new Vuex.Store({
modules,
state,
getters,
actions,
mutations
})
vuexПосле получения определенного состояния и модуля он будет отформатирован в древовидную структуру, и многие последующие операции основаны на этой древовидной структуре для операций и обработки, которую можно распечатать в любом используемом компоненте.this.$store._modules.rootнаблюдать за его структурой
Форматированная древовидная структура, каждый уровень будет содержатьstate,_rawModule,_childrenтри основных свойства
структура узла дерева
{
state:{},
_rawModule:{},
_children: {}
}
state
Корневой модуль будет обертывать себя и все содержащиеся в нем подмодули.stateДанные размещаются в древовидной структуре в соответствии с уровнем модуля, а корневой модульstateБудет содержать себя и все данные подмодуля,stateбудет содержать данные для себя и своих подмодулей
{
state: {
count: 0,
moduleA: {
a: 'module a',
moduleB: {
b: 'module b'
}
},
moduleC: {
c: 'module c'
}
}
}
_rawModule
Каждый уровень древовидной структуры будет содержать_rawModuleузел, который вызываетstoreпередается при инициализацииoptions, в корне_rawModuleЭто все опции при инициализации, а подмодули используются при их инициализации.options
{
modules:{},
state:{},
getters:{},
actions:{},
mutations:{}
}
_children
_childrenТекущий модуль и его подмодули будут отформатированы в соответствии с согласованной древовидной структурой и помещены в родительский компонент или вместе с ним._children, имя ключа - это имя его модуля
{
moduleA:{
state: {},
_rawModule:{},
_children:{
moduleB:{
state: {},
_rawModule:{},
_children:{}
},
}
},
moduleC:{
state: {},
_rawModule:{},
_children:{}
}
}
Резюме: По призывуstoreПараметры, переданные во время инициализации, преобразуются внутри в древовидную структуру, которую можно передать черезthis.$store._modules.rootПроверить
конвертировать
Зная древовидную структуру после конвертации, давайте посмотримvuexобрабатывается в коде, вsrc/moduleпапка, существуетmodule-collection.jsа такжеmodule.jsДва документа, в основномModuleCollectionа такжеModuleДва класса для коллекции модулей
Module
ModuleОсновная функция состоит в том, чтобы сгенерировать соответствующую структуру узла в соответствии с разработанной структурой узла дерева.После создания экземпляра будет сгенерирована базовая структура данных, и для ее прототипа будут установлены некоторые методы работы для вызова экземпляра.
import { forEachValue } from '../util'
export default class Module {
constructor (rawModule, runtime) {
// 是否为运行时 默认为true
this.runtime = runtime
// _children 初始化是一个空对象
this._children = Object.create(null)
// 将初始化vuex的时候 传递的参数放入_rawModule
this._rawModule = rawModule
// 将初始化vuex的时候 传递的参数的state属性放入state
const rawState = rawModule.state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
// _children 初始化是一个空对象,为其增加子模块
addChild (key, module) {
this._children[key] = module
}
// 根据 key,获取对应的模块
getChild (key) {
return this._children[key]
}
}
ModuleCollection
комбинироватьModuleиспользуется для создания древовидных структур
import Module from './module'
import { forEachValue } from '../util'
export default class ModuleCollection {
constructor (rawRootModule) {
// 根据options 注册模块
this.register([], rawRootModule, false)
}
// 利用 reduce,根据 path 找到此时子模块对应的父模块
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
register (path, rawModule, runtime = true) {
// 初始化一个节点
const newModule = new Module(rawModule, runtime)
if (path.length === 0) { // 根节点, 此时 path 为 []
this.root = newModule
} else { // 子节点处理
// 1. 找到当前子节点对应的父
// path ==> [moduleA, moduleC]
// path.slice(0, -1) ==> [moduleA]
// get ==> 获取到moduleA
const parent = this.get(path.slice(0, -1))
// 2. 调用 Module 的 addChild 方法,为其 _children 增加节点
parent.addChild(path[path.length - 1], newModule)
}
// 若是存在子模块,则会遍历递归调用 register
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
}
- инициализация
ModuleCollectionФактический параметр передан, когдаnew Vuex.Store({....options})серединаoptions, В настоящее времяrawRootModuleто естьoptions, следующие операции основаны наrawRootModuleзамок
optionsаббревиатура структуры данных
{
modules: {
moduleA:{
modules: {
moduleB:{
}
}
},
moduleC: {
}
}
}
-
воплощать в жизнь
this.register([], rawRootModule, false)[]соответствующий параметрpath, который сохраняет иерархический путь текущего модуля, напримерmoduleBсоответствующий путь["moduleA", "moduleB"]rawRootModuleсоответствующий параметрrawModule, представляющий параметр инициализацииoptionsСоответствующие данные в , напримерmoduleAсоответствующийrawModuleдля:moduleA:{ state: { a: 'module a' }, mutations:{ incrementA: ({ commit }) => commit('increment'), decrementA: ({ commit }) => commit('decrement'), }, modules: { moduleB:{ state: { b: 'module b' } } } } -
каждое исполнение
registerсозданныйModule, который генерирует дерево узловnewModule, то, судя поpathдлина, чтобы решитьnewModuleгде разместить первое исполнениеregisterВремяpathдля[], то сразуnewModuleназначить наthis.root, в остальных случаях черезpathНайдите родительский узел, соответствующий текущему узлу, и поместите его в_childrenсередина -
судить
rawModule.modulesСуществует ли он, если есть подмодуль, он будет пройденrawModule.modulesсделать рекурсивный вызовregisterВыполните рекурсивную обработку, которая в конечном итоге сгенерирует желаемую древовидную структуру.
Store
Пройдя через предзнаменование, наконец, прибылvuexосновной классstore,существуетstoreбудет определено вstate,mutations,actions,gettersждать обработки
Первый взглядStoreобщая структура
class Store {
constructor (options = {}) {}
get state () {}
set state (v) {}
commit (_type, _payload, _options) {}
dispatch (_type, _payload) {}
subscribe (fn) {}
subscribeAction (fn) {}
watch (getter, cb, options) {}
replaceState (state) {}
registerModule (path, rawModule, options = {}) {}
unregisterModule (path) {}
hotUpdate (newOptions) {}
_withCommit (fn) {}
}
В использованииvuex, вы увидите, что часто используемые методы и свойства определены вstoreНа занятиях постепенно реализуются основные функции путем совершенствования содержания на занятии
State
определено в модулеstateпройти черезvuxПосле обработки вы можетеvueпрошедший$store.state.xxxИспользуется и будет управлять обновлениями представлений при изменении данных.
первый вstoreинициализировать в
class Store {
constructor(options) {
// 定义一些内部的状态 ....
this._modules = new ModuleCollection(options)
const state = this._modules.root.state
// 初始化根模块,会递归注册所有的子模块
installModule(this, state, [], this._modules.root)
// 初始化 store、vm
resetStoreVM(this, state)
}
// 利用类的取值函数定义state,其实取的值是内部的_vm伤的数据,代理模式
get state() {
return this._vm._data.$state
}
_withCommit (fn) {
fn()
}
}
будет выполняться первымinstallModule, рекурсивный вызов зарегистрирует данные всех подмодулей, функция будет рекурсивно вызывать себя, чтобы упростить атрибуты подмодулей, и, наконец, имена модулей всех подмодулей будут использоваться в качестве ключей.stateВ качестве соответствующего значения вложен уровень вложенности модуля, и, наконец, создается желаемая структура вложенности данных.
{
count: 0,
moduleA: {
a: "module a",
moduleB: {
b: "module b"
}
},
moduleC: {
c: "module c"
}
}
installModuleОб обработкеstateОсновной код выглядит следующим образом
/**
* @param {*} store 整个store
* @param {*} rootState 当前的根状态
* @param {*} path 为了递归使用的,路径的一个数组
* @param {*} module 从根模块开始安装
* @param {*} hot
*/
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length // 是不是根节点
// 设置 state
// 非根节点的时候 进入,
if (!isRoot && !hot) {
// 1. 获取到当前模块的父模块
const parentState = getNestedState(rootState, path.slice(0, -1))
// 2. 获取当前模块的模块名
const moduleName = path[path.length - 1]
// 3. 调用 _withCommit ,执行回调
store._withCommit(() => {
// 4. 利用Vue的特性,使用 Vue.set使刚设置的键值也具备响应式,否则Vue监控不到变化
Vue.set(parentState, moduleName, module.state)
})
}
// 递归处理子模块的state
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
БудуstateПосле обработки в нужную структуру он будет объединятьсяresetStoreVMправильноstateДля обработки, если состояние переменной определено непосредственно в Store, ее можно получить извне, но при ее изменении привязка данных vue не может использоваться для управления обновлением представления, поэтому использование характеристик vue , экземпляр vue помещается в _vm, а затем используется функция значения класса для получения
когда используешь$store.state.countКогда функция значения класса является первойget stateВозьмите значение, значение, возвращаемое функцией значения, равноresetStoreVMназначенный_vm, в сочетании сvueОтзывчивая обработка
function resetStoreVM(store, state) {
store._vm = new Vue({
data: {
$state: state
}
})
}
Mutations
существуетvuex, рекомендуется использовать при синхронном изменении состояния данныхmutationsИзмените его, не рекомендуется использовать его напрямуюthis.$store.state.xxx = xxxВнесите изменения, чтобы включить строгий режимstrict: trueобрабатывать
vuexдляmutationsОбработка разделена на две части, первый шаг заключается вinstallModuleкогда все модулиmutationsЧтобы собрать подписки, второй шагStoreнезащищенныйcommitМетод публикует и выполняет соответствующий метод
подписка
первый вStoreДобавить один в обработке_mutationsАтрибуты
constructor(options){
// 创建一个_mutations 空对象,用于收集各个模块中的 mutations
this._mutations = Object.create(null)
}
существуетinstallModuleОбработать все рекурсивные вызовы вmutations
const local = module.context = makeLocalContext(store, '', path)
// 处理 mutations
module.forEachMutation((mutation, key) => {
registerMutation(store, key, mutation, local)
})
существуетregisterMutationсоответствующий в функцииnutationsсобирать
// 注册 mutations 的处理 -- 订阅
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)
})
}
Все модули на данный моментmutationsбудет подписан на_mutations, вам нужно только найти соответствующийmutationsВыполнение обхода, где массив используется для сбора подписок, потому что вvuex, определенные в разных модулях с тем же именемmutationsбудет выполняться последовательно, поэтому вам нужно использовать подписку на массив и пройти вызов, поэтому также рекомендуется использоватьvuexКогда проект имеет определенную сложность и объем, рекомендуется использовать пространство именnamespaced: true, что может уменьшить ненужное дублированиеmutationsВсе выполняются, что приводит к неконтролируемым проблемам
makeLocalContextфункция будетvuexВозможность обработки, опустить код, чтобы открыть пространство имен, в основном дляgettersа такжеstateугон
function makeLocalContext (store, namespace, path) {
const local = {
dispatch: store.dispatch,
commit: store.commit
}
// getters 和 state 必须是懒获取,因为他们的修改会通过vue实例的更新而变化
Object.defineProperties(local, {
getters: {
get: () => store.getters
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
выпускать
После того, как подписка на коллекцию будет завершена, вам необходимоStoreПредоставьте метод для запуска выпуска, выполняя соответствующую функцию для изменения состояния данных.
первый вStoreопределить классcommitметод
{
// 触发 mutations
commit (_type, _payload, _options) {
// 1. 区分不同的调用方式 进行统一处理
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
// 2. 获取到对应type的mutation方法,便利调用
const entry = this._mutations[type]
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
}
}
Однако в исходном коде при вызове внешнего мира вызывающий класс напрямую не вызывается.commitметод, но построениеconstructorпереписан вcommit
constructor(options){
// 1. 获取 commit 方法
const { commit } = this
// 2. 使用箭头函数和call 保证this的指向
this.commit = (type, payload, options) => {
return commit.call(this, type, payload, options)
}
}
unifyObjectStyleМетод выполняет процесс форматирования параметров, и вызов мутаций может использоватьthis.$store.commit('increment', payload)а такжеthis.$store.commit({type: 'increment', payload})Функция unifyObjectStyle предназначена для форматирования различных параметров в одну ситуацию двумя способами:actionsпо аналогии
function unifyObjectStyle (type, payload, options) {
if (isObject(type) && type.type) {
options = payload
payload = type
type = type.type
}
return { type, payload, options }
}
Actions
actionsдля обработки асинхронных изменений данных,mutationsОн используется для обработки синхронных изменений данных.Разница между ними заключается в том, обрабатываются ли данные асинхронно.Поэтому они имеют много общего в реализации.actionsВыполните сбор подписки, а затем предоставьте методы для публикации и выполнения.
подписка
первый вStoreДобавить один в обработке_actionsАтрибуты
constructor(options){
// 创建一个 _actions 空对象,用于收集各个模块中的 actions
this._actions = Object.create(null)
}
существуетinstallModuleОбработать все рекурсивные вызовы вactions
const local = module.context = makeLocalContext(store, '', path)
// 处理actions
module.forEachAction((action, key) => {
const type = action.root ? key : '' + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
существуетregisterActionсоответствующий в функцииactionsсобирать
// 注册 actions 的处理
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
// actions 和 mutations 在执行时,第一个参数接受到的不一样
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)
// 判断是否时Promise
if (!isPromise(res)) {
res = Promise.resolve(res)
}
return res
})
}
выпускать
actionsвыполнение релиза иmutationsЛечение такое же, разница в том, чтоdispatchметод требует дополнительной обработки
// 触发 actipns
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
const entry = this._actions[type]
// 若是多个,则使用Promise.all(),否则执行一次
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
// 拿到执行结果 进行判断处理
return result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {}
return res
})
}