Принцип фреймворка Vuex и анализ исходного кода

Vue.js Vuex

Vuex — это экосистема, разработанная для Vue для управления состоянием данных страницы и обеспечения унифицированных операций с данными. Он фокусируется на слое модели в шаблоне MVC и предусматривает, что все операции с данными должны проходить черезaction - mutation - state changeПроцесс выполняется, а затем объединяется с функцией двусторонней привязки представления данных Vue для реализации обновления отображения страницы. Унифицированное управление состоянием страницы и обработка операций могут сделать взаимодействие сложных компонентов простым и понятным, в то же время он может выполнять операции назад и вперед в режиме машины времени в режиме отладки и просматривать процесс изменения данных, что делает отладку кода более удобной. .

Недавно я использовал Vuex в проекте разработки для управления общим состоянием страницы и столкнулся со многими проблемами. Решил изучить исходный код, кроме ответов на вопросы, можно углубленно изучить принципы его реализации.

Сначала бросьте вопросы, чтобы сделать обучение и исследование более целенаправленным:

  1. С Vuex просто сделайтеVue.use(Vuex), и передайте пример объекта хранилища в конфигурации Vue. Как хранилище реализует внедрение?
  2. Как состояние поддерживает конфигурацию модуля и вложенность модулей?
  3. При выполнении диспетчеризации для запуска действия (аналогично фиксации) вам нужно только передать (тип, полезная нагрузка), где получено первое хранилище параметров в функции выполнения действия?
  4. Как отличить, изменено ли состояние напрямую извне или изменено методом мутации?
  5. Как реализована функция "временной и космический шаттл" при отладке?

Примечание. Эта статья более полезна для студентов, которые имеют практический опыт использования Vuex, могут более четко понять рабочий процесс и принципы Vuex, а также более легко его использовать. Для начинающих студентов вы можете сначала обратиться к Vuex.официальная документацияИзучите основные понятия.

1. Основной процесс фреймворка

Прежде чем анализировать исходный код, давайте взглянем на карту основных идей, представленную в официальной документации, которая также представляет рабочий процесс всей инфраструктуры Vuex.
vuex-core
Как показано на рисунке, Vuex создал полную экосистему для компонентов Vue, включая вызовы API в разработке. В этой экосистеме кратко опишите основные функции каждого модуля в основном процессе:

  • Компоненты Vue: компоненты Vue. На HTML-странице он отвечает за получение интерактивного поведения, такого как пользовательские операции, и за выполнение метода отправки для запуска соответствующего действия для ответа.
  • диспетчеризация: метод триггера действия, который является единственным методом, который может выполнять действие.
  • действия: модуль обработки поведения действия. Отвечает за обработку всех взаимодействий, полученных Vue Components. Содержит синхронные/асинхронные операции, поддерживает несколько методов с одинаковыми именами и запускается в порядке регистрации. Операции, запрашиваемые в фоновом API, выполняются в этом модуле, включая инициирование других действий и отправку мутаций. Этот модуль предоставляет пакет Promise для поддержки последовательного запуска действий.
  • commit: метод операции фиксации изменения состояния. Выполнение мутации — единственный способ выполнить мутацию.
  • мутации: метод операции изменения состояния. Это единственный рекомендуемый метод изменения состояния Vuex. Другие методы модификации будут сообщать об ошибках в строгом режиме. Этот метод может выполнять только синхронные операции, а имя метода может быть только глобально уникальным. Во время операции будут обнаружены некоторые крючки для мониторинга состояния и так далее.
  • state: объект-контейнер управления состоянием страницы. Централизованно храните разрозненные данные объекта данных в компонентах Vue, что является уникальным в глобальном масштабе для унифицированного управления состоянием. Данные, необходимые для отображения страницы, считываются из этого объекта, а детальный механизм ответа данных Vue используется для выполнения эффективных обновлений состояния.
  • геттеры: метод чтения объекта состояния. Этот модуль не указан отдельно на рисунке и должен быть включен в визуализацию.Компоненты Vue считывают объект глобального состояния с помощью этого метода.

Компонент Vue получает интерактивное поведение и вызывает метод отправки, чтобы инициировать обработку, связанную с действием.Если состояние страницы необходимо изменить, вызывается метод фиксации для отправки мутации для изменения состояния и нового значения состояние получается через геттеры, а компоненты Vue повторно визуализируются, и интерфейс обновляется соответствующим образом.

2. Введение в структуру каталогов

Откройте проект Vuex и посмотрите на структуру каталогов исходного кода.

dir_structure

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:
cart_vue_structure

Соответствующий поток магазина:
cart_vue_structure

В-четвертых, хранить объектную конструкцию

Приведенный выше анализ загрузки фреймворка Vuex и внедрения пользовательских объектов хранилища решил проблему.Вопрос 1. Затем подробно проанализируйте внутренние функции и конкретную реализацию объекта хранилища, чтобы ответитьПочему действия, геттеры и мутации могут получать данные, относящиеся к хранилищу, из arguments[0]?И другие вопросы.

Логика реализации объекта хранилища более сложна.Давайте сначала посмотрим на общий логический поток метода построения, чтобы помочь в понимании позже:

cart_vue_structure

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 необходимы следующие условия:

  1. Функция установки была выполнена для загрузки;
  2. Поддержка синтаксиса 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 Регистрация мутаций, действий и геттеров

После определения локальной среды циклически регистрируйте действия и мутации, которые мы настроили в опциях. Прежде чем анализировать каждую функцию регистрации по отдельности, посмотрите на блок-схему логических взаимосвязей между модулями:

complete_flow

Логика кода анализируется ниже:

// 注册对应模块的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: []
}

После загрузки приведенной выше конфигурации структура состояния страницы выглядит следующим образом:

cart_state

Настройка атрибута в состоянии осуществляется по правилам пути к модулю в конфигурации опции.Рассмотрим пример работы действия.

Часть кода компонента 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). Каждую неделю мы будем выбирать одного «отлично отвечающего» и дарить приятный маленький подарок. Приходите отсканировать код, чтобы следовать за нами!

公众号二维码