Vuex — это экосистема, разработанная для Vue для управления состоянием данных страницы и обеспечения унифицированных операций с данными. Он фокусируется на слое модели в шаблоне MVC и предусматривает, что все операции с данными должны проходить черезaction - mutation - state change
Процесс выполняется, а затем объединяется с функцией двусторонней привязки представления данных Vue для реализации обновления отображения страницы. Унифицированное управление состоянием страницы и обработка операций могут сделать взаимодействие сложных компонентов простым и понятным, в то же время он может выполнять операции назад и вперед в режиме машины времени в режиме отладки и просматривать процесс изменения данных, что делает отладку кода более удобной. .
Недавно я использовал Vuex в проекте разработки для управления общим состоянием страницы и столкнулся со многими проблемами. Решил изучить исходный код, кроме ответов на вопросы, можно углубленно изучить принципы его реализации.
Сначала бросьте вопросы, чтобы сделать обучение и исследование более целенаправленным:
- С Vuex просто сделайте
Vue.use(Vuex)
, и передайте пример объекта хранилища в конфигурации Vue. Как хранилище реализует внедрение? - Как состояние поддерживает конфигурацию модуля и вложенность модулей?
- При выполнении диспетчеризации для запуска действия (аналогично фиксации) вам нужно только передать (тип, полезная нагрузка), где получено первое хранилище параметров в функции выполнения действия?
- Как отличить, изменено ли состояние напрямую извне или изменено методом мутации?
- Как реализована функция "временной и космический шаттл" при отладке?
Примечание. Эта статья более полезна для студентов, которые имеют практический опыт использования Vuex, могут более четко понять рабочий процесс и принципы Vuex, а также более легко его использовать. Для начинающих студентов вы можете сначала обратиться к Vuex.официальная документацияИзучите основные понятия.
1. Основной процесс фреймворка
Прежде чем анализировать исходный код, давайте взглянем на карту основных идей, представленную в официальной документации, которая также представляет рабочий процесс всей инфраструктуры Vuex.
Как показано на рисунке, Vuex создал полную экосистему для компонентов Vue, включая вызовы API в разработке. В этой экосистеме кратко опишите основные функции каждого модуля в основном процессе:
- Компоненты Vue: компоненты Vue. На HTML-странице он отвечает за получение интерактивного поведения, такого как пользовательские операции, и за выполнение метода отправки для запуска соответствующего действия для ответа.
- диспетчеризация: метод триггера действия, который является единственным методом, который может выполнять действие.
- действия: модуль обработки поведения действия. Отвечает за обработку всех взаимодействий, полученных Vue Components. Содержит синхронные/асинхронные операции, поддерживает несколько методов с одинаковыми именами и запускается в порядке регистрации. Операции, запрашиваемые в фоновом API, выполняются в этом модуле, включая инициирование других действий и отправку мутаций. Этот модуль предоставляет пакет Promise для поддержки последовательного запуска действий.
- commit: метод операции фиксации изменения состояния. Выполнение мутации — единственный способ выполнить мутацию.
- мутации: метод операции изменения состояния. Это единственный рекомендуемый метод изменения состояния Vuex. Другие методы модификации будут сообщать об ошибках в строгом режиме. Этот метод может выполнять только синхронные операции, а имя метода может быть только глобально уникальным. Во время операции будут обнаружены некоторые крючки для мониторинга состояния и так далее.
- state: объект-контейнер управления состоянием страницы. Централизованно храните разрозненные данные объекта данных в компонентах Vue, что является уникальным в глобальном масштабе для унифицированного управления состоянием. Данные, необходимые для отображения страницы, считываются из этого объекта, а детальный механизм ответа данных Vue используется для выполнения эффективных обновлений состояния.
- геттеры: метод чтения объекта состояния. Этот модуль не указан отдельно на рисунке и должен быть включен в визуализацию.Компоненты Vue считывают объект глобального состояния с помощью этого метода.
Компонент Vue получает интерактивное поведение и вызывает метод отправки, чтобы инициировать обработку, связанную с действием.Если состояние страницы необходимо изменить, вызывается метод фиксации для отправки мутации для изменения состояния и нового значения состояние получается через геттеры, а компоненты Vue повторно визуализируются, и интерфейс обновляется соответствующим образом.
2. Введение в структуру каталогов
Откройте проект Vuex и посмотрите на структуру каталогов исходного кода.
Vuex предоставляет очень мощную функцию управления состоянием, но исходного кода немного, а структура каталогов очень понятна. Во-первых, давайте кратко представим функции каждого файла каталога:
- модуль: Предоставляет функцию создания объектов модулей и деревьев объектов модулей;
- плагины: предоставление плагинов для помощи в разработке, таких как функция «путешествия во времени», функция регистрации изменения состояния и т. д.;
- helpers.js: предоставляет API для поиска действий, мутаций и геттеров;
- index.js: это основной входной файл исходного кода, который обеспечивает построение и установку каждого модуля магазина;
- mixin.js: обеспечивает загрузку хранилища в экземпляре Vue;
- util.js: предоставляет методы инструментов, такие как find, deepCopy, forEachValue и assert.
3. Первоначальная загрузка и закачка
После понимания общего каталога и соответствующих функций ниже начинается анализ исходного кода.index.jsСодержит весь код ядра, начиная с этого файла для анализа.
3.1 Пример загрузки
Давайте рассмотрим простой пример:
/**
* store.js文件
* 创建store对象,配置state、action、mutation以及getter
*
**/
import Vue from 'vue'
import Vuex from 'vuex'
// install Vuex框架
Vue.use(Vuex)
// 创建并导出store对象。为了方便,不配置任何参数
export default new Vuex.Store()
В файле Store.js загружается, создается и экспортировала пустую конфигурацию экземпляра объекта магазина.
/**
* vue-index.js文件
*
*
**/
import Vue from 'vue'
import App from './../pages/app.vue'
import store from './store.js'
new Vue({
el: '#root',
router,
store,
render: h => h(App)
})
Затем в index.js инициализируйте компонент Vue на корневом уровне страницы, как обычно, и передайте этот пользовательский объект хранилища.
Такие какВопрос 1Как упоминалось выше, в дополнение к коду инициализации Vue в приведенном выше примере передается только дополнительный объект хранилища. Давайте посмотрим на реализацию в исходном коде.
3.2 Анализ нагрузки
В начале выполнения кода файла index.js определяется локальная переменная Vue, чтобы определить, была ли она загружена, и уменьшить поиск в глобальной области.
let Vue
Затем оценивается, что если он находится в среде браузера и загружен Vue, выполняется метод установки.
// auto install in dist mode
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
Метод установки загружает Vuex в объект Vue,Vue.use(Vuex)
Через него же он и выполняется, сначала посмотрите на реализацию метода Vue.use:
function (plugin: Function | Object) {
/* istanbul ignore if */
if (plugin.installed) {
return
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
// 实际执行插件的install方法
plugin.install.apply(plugin, args)
} else {
plugin.apply(null, args)
}
plugin.installed = true
return this
}
Если он загружается в первый раз, назначьте локальную переменную Vue глобальному объекту Vue и выполните метод applyMixin.Реализация установки выглядит следующим образом:
function install (_Vue) {
if (Vue) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
return
}
Vue = _Vue
applyMixin(Vue)
}
Давайте посмотрим на код внутри метода applyMixin. Если это версия 2.x.x или более поздняя, вы можете использовать форму хука для внедрения или использовать метод _init, который инкапсулирует и заменяет прототип объекта Vue для выполнения внедрения.
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
const usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
Конкретная реализация: установите хранилище, переданное при инициализации корневого компонента Vue, в свойство $store этого объекта, а дочерний компонент ссылается на свойство $store из своего родительского компонента и устанавливает его вложенным слой за слоем. Выполнить в любом компонентеthis.$store
Вы можете найти загруженный объект хранилища.Метод vuexInit реализован следующим образом:
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
Смотрите легенду, чтобы понять передачу магазина.
Структурная схема страницы Vue:
Соответствующий поток магазина:
В-четвертых, хранить объектную конструкцию
Приведенный выше анализ загрузки фреймворка Vuex и внедрения пользовательских объектов хранилища решил проблему.Вопрос 1. Затем подробно проанализируйте внутренние функции и конкретную реализацию объекта хранилища, чтобы ответитьПочему действия, геттеры и мутации могут получать данные, относящиеся к хранилищу, из arguments[0]?И другие вопросы.
Логика реализации объекта хранилища более сложна.Давайте сначала посмотрим на общий логический поток метода построения, чтобы помочь в понимании позже:
4.1 Экологическая оценка
Начинайте анализировать конструктор магазина, и разбирайте его функции одну за другой, подраздел за функцией.
constructor (options = {}) {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
Оценка среды выполняется в конструкторе хранилища.Для работы Vuex необходимы следующие условия:
- Функция установки была выполнена для загрузки;
- Поддержка синтаксиса Promise.
Функция утверждения — это простая реализация функции утверждения, которая может быть реализована в одной строке кода.
function assert (condition, msg) {
if (!condition) throw new Error(`[vuex] ${msg}`)
}
4.2 Инициализация данных, построение дерева модулей
После оценки среды внутренние данные инициализируются в соответствии с входящими параметрами или значениями по умолчанию новой конструкции.
const {
state = {},
plugins = [],
strict = false
} = options
// store internal state
this._committing = false // 是否在进行提交状态标识
this._actions = Object.create(null) // acitons操作对象
this._mutations = Object.create(null) // mutations操作对象
this._wrappedGetters = Object.create(null) // 封装后的getters集合对象
this._modules = new ModuleCollection(options) // Vuex支持store分模块传入,存储分析后的modules
this._modulesNamespaceMap = Object.create(null) // 模块命名空间map
this._subscribers = [] // 订阅函数集合,Vuex提供了subscribe功能
this._watcherVM = new Vue() // Vue组件用于watch监视变化
передачаnew Vuex.store(options)
Объект options, переданный в то время, используется для создания класса ModuleCollection Давайте посмотрим на его функции.
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.root = new Module(rawRootModule, false)
// register all nested modules
if (rawRootModule.modules) {
forEachValue(rawRootModule.modules, (rawModule, key) => {
this.register([key], rawModule, false)
})
}
ModuleCollection в основном создает объект входящих опций как объект модуля и вызывает его циклически.this.register([key], rawModule, false)
Модули регистрируются для свойства modules, благодаря чему все они становятся объектами модулей, и, наконец, объект параметров конструируется в полное дерево компонентов. В классе ModuleCollection также предусмотрена функция замены модулей, для детальной реализации можно посмотреть исходный файлmodule-collection.js.
4.3 настройки отправки и фиксации
Вернитесь к коду конструктора хранилища.
// 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)
}
Пакет заменяет методы отправки и фиксации в прототипе, указывая на текущий объект хранилища. Методы отправки и фиксации реализованы следующим образом:
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload) // 配置参数处理
// 当前type下所有action处理函数集合
const entry = this._actions[type]
if (!entry) {
console.error(`[vuex] unknown action type: ${type}`)
return
}
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
Как упоминалось ранее, функция диспетчеризации заключается в срабатывании и передаче некоторых параметров (полезной нагрузки) действию соответствующего типа. Поскольку он поддерживает 2 метода вызова, при диспетчеризации сначала выполняется обработка адаптации параметров, а затем оценивается, существует ли тип действия.this._actions[type]
и следующееthis._mutations[type]
Все они представляют собой обработанные наборы функций, а конкретный контент оставлен для последующего анализа).
По сравнению с методом отправки метод фиксации является типом триггера, но соответствующая обработка относительно сложна.Код выглядит следующим образом.
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) {
console.error(`[vuex] unknown mutation type: ${type}`)
return
}
// 专用修改state方法,其他修改state方法均是非法修改
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 订阅者函数遍历执行,传入当前的mutation对象和当前的state
this._subscribers.forEach(sub => sub(mutation, this.state))
if (options && options.silent) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
Этот метод также поддерживает 2 метода вызова. Сначала выполните адаптацию параметра, определите тип мутации триггера, используйте метод _withCommit для выполнения функции пакетной обработки мутации триггера и передайте параметр полезной нагрузки. После завершения выполнения уведомите всех _subscribers (функции подписки) об объекте мутации этой операции и текущем состоянии состояния и запросите предупреждение, если будет передан удаленный параметр без вывода сообщений.
4.4 Метод изменения состояния
_withCommit — это прокси-метод, и все операции по изменению состояния, запускающие мутацию, проходят через него, чтобы единообразно управлять изменением состояния и отслеживать его. Код реализации выглядит следующим образом.
_withCommit (fn) {
// 保存之前的提交状态
const committing = this._committing
// 进行本次提交,若不设置为true,直接修改state,strict模式下,Vuex将会产生非法修改state的警告
this._committing = true
// 执行state的修改操作
fn()
// 修改完成,还原本次修改之前的状态
this._committing = committing
}
Состояние фиксации при выполнении кеша Установите для текущего состояния значение true, а затем выполните эту операцию фиксации.После завершения операции восстановите состояние фиксации до предыдущего состояния.
4.5 установка модуля
После привязки методов отправки и комбинации выполните строгие настройки режима, а также установку модуля (InstallModule). Строгий режим рекомендует только в режиме разработки, его необходимо отключить после включения режима разработки.
// strict mode
this.strict = strict
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
4.5.1 Инициализация rootState
В примечаниях к приведенному выше коду упоминается, что метод installModule инициализирует корневой компонент дерева компонентов, регистрирует все подкомпоненты и сохраняет все геттеры в свойстве this._wrappedGetters Давайте посмотрим на реализацию кода.
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (namespace) {
store._modulesNamespaceMap[namespace] = module
}
// 非根组件设置 state 方法
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}
······
Определите, является ли это корневым каталогом и задано ли пространство имен. Если он существует, сохраните модуль в пространстве имен. Если это не корневой компонент и не является горячим условием, получите состояние родителя модуля через метод getNestedState , и получите имя модуля, где он находится, вызовитеVue.set(parentState, moduleName, module.state)
Метод устанавливает свое состояние в свойство moduleName родительского объекта состояния, тем самым реализуя государственную регистрацию модуля (первое выполнение здесь, поскольку это регистрация в корневом каталоге, метод в этом состоянии не будет выполняться). Код метода getNestedState очень прост, проанализируйте путь для получения состояния следующим образом.
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
4.5.2 настройки контекста модуля
const local = module.context = makeLocalContext(store, namespace, path)
После оценки условий пространства имен и корневого каталога определите локальную переменную и значение module.context, выполните метод makeLocalContext и установите локальную отправку, метод фиксации, геттеры и состояние для модуля (из-за существования пространства имен, требуется обработка совместимости).
4.5.3 Регистрация мутаций, действий и геттеров
После определения локальной среды циклически регистрируйте действия и мутации, которые мы настроили в опциях. Прежде чем анализировать каждую функцию регистрации по отдельности, посмотрите на блок-схему логических взаимосвязей между модулями:
Логика кода анализируется ниже:
// 注册对应模块的mutation,供state修改使用
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 注册对应模块的action,供数据操作、提交mutation等异步操作使用
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local)
})
// 注册对应模块的getters,供state读取使用
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
В методе registerMutation получается набор функций обработки, соответствующий типу мутации в хранилище, и в него проталкивается новая функция обработки. Здесь мы инкапсулируем соответствующий обработчик, который мы установили для типа мутаций, и передаем состояние исходной функции. в исполненииcommit('xxx', payload)
, все обработчики мутации типа xxx получат состояние и полезную нагрузку, поэтому состояние получается в обработчике.
function registerMutation (store, type, handler, local) {
// 取出对应type的mutations-handler集合
const entry = store._mutations[type] || (store._mutations[type] = [])
// commit实际调用的不是我们传入的handler,而是经过封装的
entry.push(function wrappedMutationHandler (payload) {
// 调用handler并将state传入
handler(local.state, payload)
})
}
Регистрация действия и геттера одинакова, посмотрите на код (Примечание: упоминалось ранееthis.actions
так же какthis.mutations
Установите это здесь).
function registerAction (store, type, handler, local) {
// 取出对应type的actions-handler集合
const entry = store._actions[type] || (store._actions[type] = [])
// 存储新的封装过的action-handler
entry.push(function wrappedActionHandler (payload, cb) {
// 传入 state 等对象供我们原action-handler使用
let res = handler({
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)
// action需要支持promise进行链式调用,这里进行兼容处理
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) {
// getters只允许存在一个处理函数,若重复需要报错
if (store._wrappedGetters[type]) {
console.error(`[vuex] duplicate getter key: ${type}`)
return
}
// 存储封装过的getters处理函数
store._wrappedGetters[type] = function wrappedGetter (store) {
// 为原getters传入对应状态
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
Обработчик действия имеет больше методов отправки и фиксации, чем обработчик мутации и геттер-оболочка, поэтому действие может выполнять действие отправки и операцию фиксации мутации.
4.5.4 Установка субмодуля
После регистрации действий, мутаций и геттеров корневого компонента он рекурсивно вызывает сам себя, чтобы зарегистрировать свое состояние, действия, мутации и геттеры для дочерних компонентов.
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
4.5.5 Комбинация экземпляров
Ранее была введена реализация методов и действий отправки и фиксации и т. д. Ниже приведена комбинация официальнойкорзинаЧасть кода в примере для углубления понимания.
Код конфигурации Vuex:
/
* store-index.js store配置文件
*
/
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import cart from './modules/cart'
import products from './modules/products'
import createLogger from '../../../src/plugins/logger'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
actions,
getters,
modules: {
cart,
products
},
strict: debug,
plugins: debug ? [createLogger()] : []
})
Часть кода конфигурации состояния каждого модуля в компонентном модуле Vuex:
/**
* cart.js
*
**/
const state = {
added: [],
checkoutStatus: null
}
/**
* products.js
*
**/
const state = {
all: []
}
После загрузки приведенной выше конфигурации структура состояния страницы выглядит следующим образом:
Настройка атрибута в состоянии осуществляется по правилам пути к модулю в конфигурации опции.Рассмотрим пример работы действия.
Часть кода компонента Vuecart:
/**
* Cart.vue 省略template代码,只看script部分
*
**/
export default {
methods: {
// 购物车中的购买按钮,点击后会触发结算。源码中会调用 dispatch方法
checkout (products) {
this.$store.dispatch('checkout', products)
}
}
}
Часть компонентов COMPORTION CANGED VEXCART.JS Action:
const actions = {
checkout ({ commit, state }, products) {
const savedCartItems = [...state.added] // 存储添加到购物车的商品
commit(types.CHECKOUT_REQUEST) // 设置提交结算状态
shop.buyProducts( // 提交api请求,并传入成功与失败的cb-func
products,
() => commit(types.CHECKOUT_SUCCESS), // 请求返回成功则设置提交成功状态
() => commit(types.CHECKOUT_FAILURE, { savedCartItems }) // 请求返回失败则设置提交失败状态
)
}
}
Нажмите, чтобы купить в компоненте Vue, чтобы выполнить метод отправки текущего модуля. Значение входящего типа — «checkout», а значение полезной нагрузки — «продукты». В исходном коде метод отправки ищет соответствующий массив выполнения 'checkout' во всех зарегистрированных действиях и выводит его из выполнения цикла. Выполняется инкапсулированный метод с именемwrappedActionHandler.В методеwrappedActionHandler выполняется функция выполнения фактической входящей кассы.
function wrappedActionHandler (payload, cb) {
let res = handler({
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)
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
}
}
Здесь обработчиком является входящая функция проверки. Сюда передаются фиксация и состояние, необходимые для ее выполнения, а также передается полезная нагрузка. В экземпляре полученный параметр называется products. Выполнение фиксации такое же. В этом примере проверка также выполняет операцию фиксации и отправляет модификацию, значение типа которой равно types.CHECKOUT_REQUEST. Поскольку имя мутации уникально, здесь создается константная форма вызова, чтобы предотвратить дублирование имена, и выполнение следует за последовательностью анализа исходного кода, вызовfunction wrappedMutationHandler (payload) {
handler(local.state, payload)
}
Обертывает функцию для фактического вызова сконфигурированного метода мутации.
Увидев анализ исходного кода и приведенный выше небольшой пример, вы сможете понять принцип работы действия отправки и фиксации изменения. Затем просмотрите исходный код, чтобы увидеть, как геттеры реализуют доступ к состоянию в реальном времени.
4.6 настройки компонента store._vm
После выполнения установки каждого модуля выполните метод resetStoreVM для инициализации компонента хранилища.
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)
Основываясь на предыдущем анализе, мы видим, что Vuex на самом деле строит виртуальный компонент с именем store.Все настроенные состояния, действия, мутации и геттеры являются атрибутами его компонентов, и все операции выполняются над этим виртуальным компонентом.
Давайте взглянем на внутреннюю реализацию метода resetStoreVM.
function resetStoreVM (store, state) {
const oldVm = store._vm // 缓存前vm组件
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
// 循环所有处理过的getters,并新建computed对象进行存储,通过Object.defineProperty方法为getters对象建立属性,使得我们通过this.$store.getters.xxxgetter能够访问到该getters
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => 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设为静默模式,避免报出用户加载的某些插件触发的警告
Vue.config.silent = true
// 设置新的storeVm,将当前初始化的state以及getters作为computed属性(刚刚遍历生成的)
store._vm = new Vue({
data: { state },
computed
})
// 恢复Vue的模式
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
// 该方法对state执行$watch以禁止从mutation外部修改state
enableStrictMode(store)
}
// 若不是初始化过程执行的该方法,将旧的组件state设置为null,强制更新所有监听者(watchers),待更新生效,DOM更新完成后,执行vm组件的destroy方法进行销毁,减少内存的占用
if (oldVm) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation.
store._withCommit(() => {
oldVm.state = null
})
Vue.nextTick(() => oldVm.$destroy())
}
}
Метод resetStoreVm создает компонент _vm текущего экземпляра хранилища, и теперь хранилище создано. Приведенный выше код включает оценку строгого режима, давайте посмотрим, как реализован строгий режим.
function enableStrictMode (store) {
store._vm.$watch('state', () => {
assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
}, { deep: true, sync: true })
}
Очень простое приложение, отслеживающее изменения состояния, если оно не пройденоthis._withCommit()
способ изменить состояние, будет сообщено об ошибке.
4.7 внедрение плагина
Наконец, выполняется имплантация плагина.
plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
Есть 3 функции, предоставляемые devtoolPlugin:
// 1. 触发Vuex组件初始化的hook
devtoolHook.emit('vuex:init', store)
// 2. 提供“时空穿梭”功能,即state操作的前进和倒退
devtoolHook.on('vuex:travel-to-state', targetState => {
store.replaceState(targetState)
})
// 3. mutation被执行时,触发hook,并提供被触发的mutation函数和当前的state状态
store.subscribe((mutation, state) => {
devtoolHook.emit('vuex:mutation', mutation, state)
})
На этом этапе анализа исходного кода в основном был проанализирован принцип реализации платформы Vuex.
V. Резюме
Наконец, мы возвращаемся к 5 вопросам, которые были заданы в начале статьи.
1. просить: С Vuex просто такVue.use(Vuex)
, и передайте пример объекта хранилища в конфигурации Vue. Как хранилище реализует внедрение?
отвечать:
Vue.use(Vuex)
Метод выполняет метод установки, который реализует инкапсуляцию и внедрение метода инициализации объекта экземпляра Vue, так что входящий объект хранилища устанавливается в $store контекста Vue. Так что где угодно в Vue Component может пройтиthis.$store
доступ к магазину.
2. просить:Внутреннее состояние поддерживает конфигурацию модулей и вложенность модулей, как это реализовано?
отвечать: В методе построения магазина есть метод makeLocalContext, у всех модулей будет локальный контекст, который сопоставляется по пути при настройке. Итак, выполните как
dispatch('submitOrder', payload)
В этом типе действия локальное состояние модуля получается по умолчанию.Если вы хотите получить доступ к состоянию самого внешнего слоя или других модулей, вы можете получить к нему доступ только шаг за шагом из rootState в соответствии с путем пути.
3. просить:При выполнении диспетчеризации для запуска действия (аналогично фиксации) вам нужно только передать (тип, полезная нагрузка), где получено первое хранилище параметров в функции выполнения действия?
отвечать: при инициализации хранилища все настроенные действия, мутации и геттеры инкапсулируются. выполнение как
dispatch('submitOrder', payload)
, все методы обработки типом submitOrder в действиях инкапсулированы, а их первый параметр — текущий объект хранилища, поэтому его можно получить{ dispatch, commit, state, rootState }
данные.
4. просить:Как Vuex различает, было ли состояние изменено напрямую извне или изменено с помощью метода мутации?
отвечать: единственный способ изменить состояние в Vuex — выполнить
commit('xx', payload)
метод, базовый слой которого выполняетсяthis._withCommit(fn)
Установите для переменной флага _committing значение true, а затем измените состояние.После модификации вам необходимо восстановить переменную _committing. Хотя внешняя модификация может напрямую изменять состояние, она не изменяет бит флага _committing, поэтому, пока вы наблюдаете за состоянием и решаете, истинно ли значение _committing при изменении состояния, вы можете судить о законности модификации.
5. просить:Как реализована функция "временной и космический шаттл" при отладке?
отвечать: эта функциональность доступна в devtoolPlugin. Поскольку все изменения состояния в режиме разработки будут записаны, функция «временного и космического челнока» фактически заменяет текущее состояние состоянием состояния в определенный момент записи, используя
store.replaceState(targetState)
метод будет выполнятьсяthis._vm.state = state
выполнить.
В исходном коде также есть некоторые инструментальные функции, такие как registerModule, unregisterModule, hotUpdate, watch, and subscribe и т. д. Если вам интересно, вы можете открыть исходный код, чтобы увидеть его, и не будем здесь подробно описываться.
6. Об авторе
Мин Йи, старший инженер отдела исследований и разработок Meituan Takeaway, присоединился к Meituan Takeaway в 2014 году и отвечает за разработку основного веб-сайта. После участия в разработке B-side, C-side, дистрибуции и других полнофункциональных систем для еды на вынос, в настоящее время он в основном отвечает за систему деятельности торговых купонов.
Наконец, прикрепите твердый и широкий, Meituan Takeaway давно ищет старших инженеров по интерфейсу / технических экспертов по интерфейсу Добро пожаловать, чтобы отправить свое резюме по адресу: mabinbing02#meituan.com.
Ответить на «мыслительные вопросы», найти ошибки в статье и задать вопросы по содержанию, вы можете оставить сообщение в фоновом режиме общедоступной учетной записи WeChat (техническая команда Meituan Dianping). Каждую неделю мы будем выбирать одного «отлично отвечающего» и дарить приятный маленький подарок. Приходите отсканировать код, чтобы следовать за нами!