Изучите общую архитектуру исходного кода vuex и создайте собственную библиотеку управления состоянием.

исходный код JavaScript

предисловие

Здравствуйте, яВакагава. Это学习源码整体架构Статья 5. Слово «общая архитектура» кажется немного большим, скажем так, это общая структура исходного кода. В этой статье изучается код фактического склада.

学习源码整体架构Цикл статей следующий:

1.Изучите общую архитектуру исходного кода jQuery и создайте собственную библиотеку js.
2.Изучите общую архитектуру исходного кода подчеркивания и создайте собственную библиотеку классов функционального программирования.
3.Изучите общую архитектуру исходного кода lodash и создайте собственную библиотеку классов функционального программирования.
4.Изучите общую архитектуру исходного кода sentry и создайте собственный SDK для мониторинга исключений переднего плана.
5.Изучите общую архитектуру исходного кода vuex и создайте собственную библиотеку управления состоянием.
6.Изучите общую архитектуру исходного кода axios и создайте собственную библиотеку запросов.
7.Изучите общую архитектуру исходного кода koa, проанализируйте принцип луковой модели koa и принцип совместной работы.
8.Изучите общую архитектуру исходного кода Redux и глубоко поймите принципы Redux и его промежуточного программного обеспечения.

Заинтересованные читатели могут нажать, чтобы прочитать. Следующая статья может быть обучениеaxiosисходный код.

Управляемое чтение
В статье более подробноvuex,vueМетоды отладки исходного кода иVuexпринцип. и подробныйVuex.useустановить иnew Vuex.Storeинициализация,Vuex.StoreвсеAPI(Такие какdispatch,commitи т. д.) реализация и вспомогательные функцииmapState,mapGetters,mapActions,mapMutations createNamespacedHelpers.

Браузер Chrome отлаживает метод исходного кода vuex

Документация Vue: отладка проектов Vue в VS Code
Отладку можно получить тем же способом из вышеописанногоvuexЭтот метод подробно описан здесь, чтобы помочь читателям, которые могут не знать, как отлаживать исходный код.
Вы можете поставить этого автораvuex-analysisХранилище анализа исходного кодаforkОдна копия или клонирование напрямую,git clone https://github.com/lxchuan12/vuex-analysis.git

какая папкаvuex, является клоном официальногоvuexскладdevветвь.
На данный момент (ноябрь 2019 г.) версияv3.1.2,последний разcommitдаba2ff3a3,2019-11-11 11:51 Ben Hutton.
Заметки автора включены для простоты понимания.

После завершения клонирования вvuex/examples/webpack.config.jsдобавлено вdevtoolконфигурация.

// 新增devtool配置,便于调试
devtool: 'source-map',
output: {}
git clone https://github.com/lxchuan12/vuex-analysis.git
cd vuex
npm i
npm run dev

Откройте http://локальный:8080/
Нажмите на пример, который вы хотите открыть, например: Корзина => http://localhost:8080/shopping-cart/
Откройте исходный код панели управления и найдите файл хранилища каталога webapck// .src слева, и вы сможете отлаживать его в соответствии со своими потребностями.

Эта статья в основном черезShopping Cart,(дорожкаvuex/examples/shopping-cart) пример отладочного кода.

Кстати, как отлаживать исходный код vue (v2.6.10)

git clone https://github.com/vuejs/vue.git

После клонированияpackage.jsonв файлеscript devдобавьте это после команды--sourcemap.

{
  "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap"
}
git clone https://github.com/vuejs/vue.git
cd vue
npm i
# 在 dist/vue.js 最后一行追加一行 //# sourceMappingURL=vue.js.map
npm run dev
# 新终端窗口
# 根目录下 全局安装http-server(一行命令启动服务的工具)
npm i -g http-server
hs -p 8100

# 在examples 文件夹中把引用的vuejs的index.html 文件 vue.min.js 改为 vue.js
# 或者把dist文件夹的 vue.min.js ,替换成npm run dev编译后的dist/vue.js 

# 浏览器打开 open http://localhost:8100/examples/

# 打开控制面板 source 在左侧找到  src 目录 即vue.js源码文件 根据自己需求断点调试即可。

В этом разделе подробно описывается метод отладки. Потому что это действительно важно. Будет проще отлаживать код и смотреть исходный код. Обратите внимание на основной отладочный код, его легко понять.
Настоятельно рекомендуется клонировать авторский репозиторий, отлаживать код самостоятельно, смотреть комментарии, не отлаживать код, просто читать статью не легко усваивается и переваривается.
Автор также читал статьи, рекомендованные автором в конце статьи, но вам все равно нужно самому читать исходный код, чтобы знать, где эти статьи написаны, а где нет подробностей.

Текст начинается~

принцип vuex

Кратко объяснитьvuexпринцип

<template>
<div>
  count {{$store.state.count}}
</div>
</template>

каждый компонент (т.Vue实例)существуетbeforeCreateЖизненный цикл обоих миксинов (Vue.mixin) с одним и тем жеStore实例как атрибут$store, Именно поэтому можно пройтиthis.$store.dispatchи т.д. Причина вызова метода.

Последний отображаемый в шаблоне$store.state.countИсходный код такой.

class Store{
  get state () {
    return this._vm._data.$state
  }
}

На самом деле это:vm.$store._vm._data.?state.countвvm.$store._vm._data.?stateотзывчив. Как добиться отзывчивости? На самом деле этоnew Vue()

function resetStoreVM (store, state, hot) {
  //  省略若干代码
  store._vm = new Vue({
    data: {
      $state: state
    },
    computed
  })
  //  省略若干代码
}

здесьstateопределяется пользователемstate. здесьcomputedобрабатывается пользователемgetters. а такжеclass StoreНекоторые из функций (API) в нем в основном связаны с модификацией.vm.$store._vm._data.?stateа такжеcomputed(getter)Услуги.

установка vue.use

Автор нарисовал картинку, чтобы показать следующееVuexобъект, этоVueплагин из .

Vuex 对象关系图
Диаграмма отношений объектов Vuex

Смотрите здесь, поздравляю, вы понялиVuexпринцип. Статья относительно длинная, если не хотите пока обращать внимание на детали исходного кода, можете клонировать код этого складаgit clone https://github.com/lxchuan12/vuex-analysis.git, последующая отладка кода, лайк и избранное, когда вы захотите посмотреть его снова.

Документация Vue.use Vue.use(Vuex)

параметр:Object Functionплагин Применение:
Установите плагин Vue.js. Если плагин является объектом, необходимо указатьinstallметод. Если плагин является функцией, он будет использоваться какinstallметод.installКогда метод вызывается, Vue передается в качестве параметра.
Этот метод нужно вызватьnew Vue()звонили раньше.
когдаinstallЕсли метод вызывается одним и тем же плагином несколько раз, плагин будет установлен только один раз.

Отладка по точкам останова, посмотритеVue.useисходный код.

function initUse (Vue) {
  Vue.use = function (plugin) {
    var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
    // 如果已经存在,则直接返回this也就是Vue
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    var args = toArray(arguments, 1);
    // 把 this(也就是Vue)作为数组的第一项
    args.unshift(this);
    // 如果插件的install属性是函数,调用它
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args);
    } else if (typeof plugin === 'function') {
      // 如果插件是函数,则调用它
      // apply(null) 严格模式下 plugin 插件函数的 this 就是 null
      plugin.apply(null, args);
    }
    // 添加到已安装的插件
    installedPlugins.push(plugin);
    return this
  };
}

установить функцию

vuex/src/store.js

export function install (_Vue) {
  // Vue 已经存在并且相等,说明已经Vuex.use过
  if (Vue && _Vue === Vue) {
    // 省略代码:非生产环境报错,vuex已经安装
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

см. далееapplyMixinфункция

функция applyMixin

vuex/src/mixin.js

export default function (Vue) {
  // Vue 版本号
  const version = Number(Vue.version.split('.')[0])
  if (version >= 2) {
    // 合并选项后 beforeCreate 是数组里函数的形式  [ƒ,  ƒ]
    // 最后调用循环遍历这个数组,调用这些函数,这是一种函数与函数合并的解决方案。
    // 假设是我们自己来设计,会是什么方案呢。
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // 省略1.x的版本代码 ...
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */
  function vuexInit () {
    const options = this.$options
    // store injection
    // store 注入到每一个Vue的实例中
    if (options.store) {
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

в конце концов каждыйVueЭкземпляры объектов , все имеют$storeАтрибуты. и то же самоеStoreпример.
Пример корзины:

const vm = new Vue({
  el: '#app',
  store,
  render: h => h(App)
})
console.log('vm.$store === vm.$children[0].$store', vm.$store === vm.$children[0].$store)
// true
console.log('vm.$store === vm.$children[0].$children[0].$store', vm.$store === vm.$children[0].$children[0].$store)
// true
console.log('vm.$store === vm.$children[0].$children[1].$store', vm.$store === vm.$children[0].$children[1].$store)
// true

Конструктор Vuex.Store

посмотри в концеnew Vuex.StoreПослеStoreДиаграмма отношения объектов экземпляра: сначала создайте общее впечатление.new Vuex.Store之后的 Store 实例对象关系图

export class Store {
  constructor (options = {}) {
    // 这个构造函数比较长,这里省略,后文分开细述
  }
}
if (!Vue && typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

еслиcdn scriptспособ представитьvuexПлагины устанавливаются автоматическиvuexплагин, не нуженVue.use(Vuex)установить.

// asset 函数实现
export function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}
if (process.env.NODE_ENV !== 'production') {
  // 可能有读者会问:为啥不用 console.assert,console.assert 函数报错不会阻止后续代码执行
  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.`)
}

Условное утверждение: если оно не удовлетворено, выдать ошибку напрямую

1. Должен использоватьVue.use(Vuex)Создайтеstoreпример.
2. Текущая среда не поддерживаетPromise, сообщается об ошибке:vuexнужноPromise polyfill.
3.Storeфункция должна использоватьnewвызов оператора.

const {
  // 插件默认是空数组
  plugins = [],
  // 严格模式默认是false
  strict = false
} = options

из определенного пользователемnew Vuex.Store(options)выигратьpluginsа такжеstrictпараметр.

// store internal state
// store 实例对象 内部的 state
this._committing = false
// 用来存放处理后的用户自定义的actoins
this._actions = Object.create(null)
// 用来存放 actions 订阅
this._actionSubscribers = []
// 用来存放处理后的用户自定义的mutations
this._mutations = Object.create(null)
// 用来存放处理后的用户自定义的 getters
this._wrappedGetters = Object.create(null)
// 模块收集器,构造模块树形结构
this._modules = new ModuleCollection(options)
// 用于存储模块命名空间的关系
this._modulesNamespaceMap = Object.create(null)
// 订阅
this._subscribers = []
// 用于使用 $watch 观测 getters
this._watcherVM = new Vue()
// 用来存放生成的本地 getters 的缓存
this._makeLocalGettersCache = Object.create(null)

утверждениеStoreЭкземпляр объекта с некоторыми внутренними переменными. Используется для хранения пользовательскихactions,mutations,gettersи т.д. переменные.

упомянутьObject.create(null)а также{}разница. У первого нет цепочки прототипов, у второго есть. которыйObject.create(null).__proto__даundefined ({}).__proto__даObject.prototype

// 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)
}

связать себяcommitа такжеdispatch

Зачем так связываться?
Описание вызоваcommitа такжеdispachизthisНе обязательноstoreПример
Это делается для того, чтобы в этих двух функцияхthisдаstoreПример

// 严格模式,默认是false
this.strict = strict
// 根模块的state
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)

приведенный выше кодinstallModule(this, state, [], this._modules.root)

Инициализируйте корневой модуль.
И зарегистрируйте все подмодули, которые также являются рекурсивными.
и собери все модулиgettersпомещатьthis._wrappedGettersв.

resetStoreVM(this, state)

инициализацияstore._vmотзывчивый
и зарегистрируйтесь_wrappedGettersтак какcomputedсвойства

plugins.forEach(plugin => plugin(this))

плагин: поместить объект экземпляраstoreПередано функции плагина для выполнения всех плагинов.

const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
  devtoolPlugin(this)
}

инициализацияvue-devtoolИнструменты разработки.
параметрdevtoolsпрошел выборdevtoolsВ противном случае возьмитеVue.config.devtoolsконфигурация.

Сначала прочтите полный исходный код этого конструктора. Вы обнаружите, что есть три места, на которых нужно сосредоточиться. Они есть:

this._modules = new ModuleCollection(options)
installModule(this, state, [], this._modules.root)
resetStoreVM(this, state)

При чтении вы можете отлаживать с помощью точек останова, операторов присваиванияthis._modules = new ModuleCollection(options), если вы не хотите временно его читать, вы можете напрямую увидеть возвращенный результат.installModule,resetStoreVMФункции можно отлаживать с помощью точек останова.

class ModuleCollection

Соберите модули и постройте древовидную структуру модулей.

прописать параметры корневого модуляrawRootModuleто естьVuex.Storeизoptionsпараметр
необработанный модуль (определяется пользователем), корневой модуль

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }
}
/**
  * 注册模块
  * @param {Array} path 路径
  * @param {Object} rawModule 原始未加工的模块
  * @param {Boolean} runtime runtime 默认是 true
  */
register (path, rawModule, runtime = true) {
  // 非生产环境 断言判断用户自定义的模块是否符合要求
  if (process.env.NODE_ENV !== 'production') {
    assertRawModule(path, rawModule)
  }

  const newModule = new Module(rawModule, runtime)
  if (path.length === 0) {
    this.root = newModule
  } else {
    const parent = this.get(path.slice(0, -1))
    parent.addChild(path[path.length - 1], newModule)
  }

  // register nested modules
  // 递归注册子模块
  if (rawModule.modules) {
    forEachValue(rawModule.modules, (rawChildModule, key) => {
      this.register(path.concat(key), rawChildModule, runtime)
    })
  }
}

class Module

// Base data struct for store's module, package with some attribute and method
// store 的模块 基础数据结构,包括一些属性和方法
export default class Module {
  constructor (rawModule, runtime) {
    // 接收参数 runtime
    this.runtime = runtime
    // Store some children item
    // 存储子模块
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    // 存储原始未加工的模块
    this._rawModule = rawModule
    // 模块 state
    const rawState = rawModule.state

    // Store the origin module's state
    // 原始Store 可能是函数,也可能是是对象,是假值,则赋值空对象。
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
}

После серии регистраций, наконецthis._modules = new ModuleCollection(options) this._modulesЗначение такое. Автор нарисовал картинку, показывающую:ModuleCollection

функция installModule

function installModule (store, rootState, path, module, hot) {
  // 是根模块
  const isRoot = !path.length
  // 命名空间 字符串
  const namespace = store._modules.getNamespace(path)
  if (module.namespaced) {
    // 省略代码: 模块命名空间map对象中已经有了,开发环境报错提示重复
    // module 赋值给 _modulesNamespaceMap[namespace]
    store._modulesNamespaceMap[namespace] = module
  }
  // ... 后续代码 移出来 待读解释
}

состояние регистрации

// set state
// 不是根模块且不是热重载
if (!isRoot && !hot) {
  // 获取父级的state
  const parentState = getNestedState(rootState, path.slice(0, -1))
  // 模块名称
  // 比如 cart
  const moduleName = path[path.length - 1]
  // state 注册
  store._withCommit(() => {
    // 省略代码:非生产环境 报错 模块 state 重复设置
    Vue.set(parentState, moduleName, module.state)
  })
}

Конечным результатом является отзывчивый экземпляр данных со структурой, аналогичной этому Store.state, например:

{
  // 省略若干属性和方法
  // 这里的 state 是只读属性 可搜索 get state 查看,上文写过
  state: {
    cart: {
      checkoutStatus: null,
      items: []
    }
 }
}
const local = module.context = makeLocalContext(store, namespace, path)

module.contextЭто задание в основном даетсяhelpersсерединаmapState,mapGetters,mapMutations,mapActionsИспользуются четыре вспомогательные функции.
Сгенерируйте локальную отправку, фиксацию, геттеры и состояние.
Основная функция — сглаживание различий, и пользователям не нужно повторно передавать параметры модуля.

Прохождение зарегистрированных мутаций

module.forEachMutation((mutation, key) => {
  const namespacedType = namespace + key
  registerMutation(store, namespacedType, mutation, local)
})
/**
 * 注册 mutation
 * @param {Object} store 对象
 * @param {String} type 类型
 * @param {Function} handler 用户自定义的函数
 * @param {Object} local local 对象
 */
function registerMutation (store, type, handler, local) {
  // 收集的所有的mutations找对应的mutation函数,没有就赋值空数组
  const entry = store._mutations[type] || (store._mutations[type] = [])
  // 最后 mutation
  entry.push(function wrappedMutationHandler (payload) {
    /**
     * mutations: {
     *    pushProductToCart (state, { id }) {
     *        console.log(state);
     *    }
     * }
     * 也就是为什么用户定义的 mutation 第一个参数是state的原因,第二个参数是payload参数
     */
    handler.call(store, local.state, payload)
  })
}

Обход зарегистрированных действий

module.forEachAction((action, key) => {
  const type = action.root ? key : namespace + key
  const handler = action.handler || action
  registerAction(store, type, handler, local)
})
/**
* 注册 mutation
* @param {Object} store 对象
* @param {String} type 类型
* @param {Function} handler 用户自定义的函数
* @param {Object} local local 对象
*/
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  // payload 是actions函数的第二个参数
  entry.push(function wrappedActionHandler (payload) {
    /**
     * 也就是为什么用户定义的actions中的函数第一个参数有
     *  { dispatch, commit, getters, state, rootGetters, rootState } 的原因
     * actions: {
     *    checkout ({ commit, state }, products) {
     *        console.log(commit, state);
     *    }
     * }
     */
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    /**
     * export function isPromise (val) {
        return val && typeof val.then === 'function'
      }
     * 判断如果不是Promise Promise 化,也就是为啥 actions 中处理异步函数
        也就是为什么构造函数中断言不支持promise报错的原因
        vuex需要Promise polyfill
        assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
     */
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    // devtool 工具触发 vuex:error
    if (store._devtoolHook) {
      // catch 捕获错误
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        // 抛出错误
        throw err
      })
    } else {
      // 然后函数执行结果
      return res
    }
  })
}

Обход зарегистрированных геттеров

module.forEachGetter((getter, key) => {
  const namespacedType = namespace + key
  registerGetter(store, namespacedType, getter, local)
})
/**
 * 注册 getter
 * @param {Object} store  Store实例
 * @param {String} type 类型
 * @param {Object} rawGetter  原始未加工的 getter 也就是用户定义的 getter 函数
 * @examples  比如 cartProducts: (state, getters, rootState, rootGetters) => {}
 * @param {Object} local 本地 local 对象
 */
function registerGetter (store, type, rawGetter, local) {
  // 类型如果已经存在,报错:已经存在
  if (store._wrappedGetters[type]) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  // 否则:赋值
  store._wrappedGetters[type] = function wrappedGetter (store) {
    /**
     * 这也就是为啥 getters 中能获取到  (state, getters, rootState, rootGetters)  这些值的原因
     * getters = {
     *      cartProducts: (state, getters, rootState, rootGetters) => {
     *        console.log(state, getters, rootState, rootGetters);
     *      }
     * }
     */
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

Обход зарегистрированных подмодулей

module.forEachChild((child, key) => {
  installModule(store, rootState, path.concat(key), child, hot)
})

функция resetStoreVM

resetStoreVM(this, state, hot)

инициализацияstore._vmотзывчивый
и зарегистрируйтесь_wrappedGettersтак какcomputedсвойства

function resetStoreVM (store, state, hot) {

  // 存储一份老的Vue实例对象 _vm
  const oldVm = store._vm

  // bind store public getters
  // 绑定 store.getter
  store.getters = {}
  // reset local getters cache
  // 重置 本地getters的缓存
  store._makeLocalGettersCache = Object.create(null)
  // 注册时收集的处理后的用户自定义的 wrappedGetters
  const wrappedGetters = store._wrappedGetters
  // 声明 计算属性 computed 对象
  const computed = {}
  // 遍历 wrappedGetters 赋值到 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.
    /**
     * partial 函数
     * 执行函数 返回一个新函数
        export function partial (fn, arg) {
          return function () {
            return fn(arg)
          }
        }
     */
    computed[key] = partial(fn, store)
    // getter 赋值 keys
    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
  // 使用一个 Vue 实例对象存储 state 树
  // 阻止警告 用户添加的一些全局mixins

  // 声明变量 silent 存储用户设置的静默模式配置
  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
  // 开启严格模式 执行这句
  // 用 $watch 观测 state,只能使用 mutation 修改 也就是 _withCommit 函数
  if (store.strict) {
    enableStrictMode(store)
  }

  // 如果存在老的 _vm 实例
  if (oldVm) {
    // 热加载为 true
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      // 设置  oldVm._data.$state = null
      store._withCommit(() => {
        oldVm._data.$state = null
      })
    }
    // 实例销毁
    Vue.nextTick(() => oldVm.$destroy())
  }
}

На данный момент исходный код конструктора прочитан, а затем посмотрите наVuex.StoreНекоторые изAPIвыполнить.

Методы экземпляра Vuex.Store

Документация API Vuex

commit

представитьmutation.

commit (_type, _payload, _options) {
  // check object-style commit
  // 统一成对象风格
  const {
    type,
    payload,
    options
  } = unifyObjectStyle(_type, _payload, _options)

  const mutation = { type, payload }
  // 取出处理后的用户定义 mutation
  const entry = this._mutations[type]
  // 省略 非生产环境的警告代码 ...
  this._withCommit(() => {
    // 遍历执行
    entry.forEach(function commitIterator (handler) {
      handler(payload)
    })
  })
  // 订阅 mutation 执行
  this._subscribers.forEach(sub => sub(mutation, this.state))

  // 省略 非生产环境的警告代码 ...
}

commitПоддерживается несколько способов. Например:

store.commit('increment', {
  count: 10
})
// 对象提交方式
store.commit({
  type: 'increment',
  count: 10
})

unifyObjectStyleФункция объединяет параметры и возвращает{ type, payload, options }.

dispatch

распределениеaction.

dispatch (_type, _payload) {
  // check object-style dispatch
  // 获取到type和payload参数
  const {
    type,
    payload
  } = unifyObjectStyle(_type, _payload)

  // 声明 action 变量 等于 type和payload参数
  const action = { type, payload }
  // 入口,也就是 _actions 集合
  const entry = this._actions[type]
  // 省略 非生产环境的警告代码 ...
  try {
    this._actionSubscribers
      .filter(sub => sub.before)
      .forEach(sub => sub.before(action, this.state))
  } catch (e) {
    if (process.env.NODE_ENV !== 'production') {
      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 result.then(res => {
    try {
      this._actionSubscribers
        .filter(sub => sub.after)
        .forEach(sub => sub.after(action, this.state))
    } catch (e) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn(`[vuex] error in after action subscribers: `)
        console.error(e)
      }
    }
    return res
  })
}

replaceState

заменятьstoreКорневое состояние , только со слиянием состояний или отладкой путешествия во времени.

replaceState (state) {
  this._withCommit(() => {
    this._vm._data.$state = state
  })
}

watch

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

/**
 * 观测某个值
 * @param {Function} getter 函数
 * @param {Function} cb 回调
 * @param {Object} options 参数对象
 */
watch (getter, cb, options) {
  if (process.env.NODE_ENV !== 'production') {
    assert(typeof getter === 'function', `store.watch only accepts a function.`)
  }
  return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
}

subscribe

подпискаstoreизmutation.

subscribe (fn) {
  return genericSubscribe(fn, this._subscribers)
}
// 收集订阅者
function genericSubscribe (fn, subs) {
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

subscribeAction

подпискаstoreизaction.

subscribeAction (fn) {
  const subs = typeof fn === 'function' ? { before: fn } : fn
  return genericSubscribe(subs, this._actionSubscribers)
}

registerModule

Зарегистрируйте динамический модуль.

/**
 * 动态注册模块
 * @param {Array|String} path 路径
 * @param {Object} rawModule 原始未加工的模块
 * @param {Object} options 参数选项
 */
registerModule (path, rawModule, options = {}) {
  // 如果 path 是字符串,转成数组
  if (typeof path === 'string') path = [path]

  // 省略 非生产环境 报错代码

  // 手动调用 模块注册的方法
  this._modules.register(path, rawModule)
  // 安装模块
  installModule(this, this.state, path, this._modules.get(path), options.preserveState)
  // reset store to update getters...
  // 设置 resetStoreVM
  resetStoreVM(this, this.state)
}

unregisterModule

Выгрузить динамический модуль.

/**
 * 注销模块
 * @param {Array|String} path 路径
 */
unregisterModule (path) {
  // 如果 path 是字符串,转成数组
  if (typeof path === 'string') path = [path]

  // 省略 非生产环境 报错代码 ...

  // 手动调用模块注销
  this._modules.unregister(path)
  this._withCommit(() => {
    // 注销这个模块
    const parentState = getNestedState(this.state, path.slice(0, -1))
    Vue.delete(parentState, path[path.length - 1])
  })
  // 重置 Store
  resetStore(this)
}

hotUpdate

горячая замена новогоactionа такжеmutation.

// 热加载
hotUpdate (newOptions) {
  // 调用的是 ModuleCollection 的 update 方法,最终调用对应的是每个 Module 的 update
  this._modules.update(newOptions)
  // 重置 Store
  resetStore(this, true)
}

Вспомогательные функции привязки компонентов

Путь к файлу:vuex/src/helpers.js

mapState

Создайте вычисляемое свойство для возвращаемого компонента.Vuex storeсостояние в .

export const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  // 非生产环境 判断参数 states  必须是数组或者是对象
  if (process.env.NODE_ENV !== 'production' && !isValidMap(states)) {
    console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
  }
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = function mappedState () {
      let state = this.$store.state
      let getters = this.$store.getters
      // 传了参数 namespace
      if (namespace) {
        // 用 namespace 从 store 中找一个模块。
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // 标记为 vuex 方便在 devtools 显示
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

normalizeNamespace нормализовать единое пространство имен

function normalizeNamespace (fn) {
  return (namespace, map) => {
    // 命名空间没传,交换参数,namespace 为空字符串
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      // 如果是字符串,最后一个字符不是 / 添加 /
      // 因为 _modulesNamespaceMap 存储的是这样的结构。
      /**
       * _modulesNamespaceMap:
          cart/: {}
          products/: {}
        }
       * */
      namespace += '/'
    }
    return fn(namespace, map)
  }
}
// 校验是否是map 是数组或者是对象。
function isValidMap (map) {
  return Array.isArray(map) || isObject(map)
}
/**
 * Normalize the map
 * 标准化统一 map,最终返回的是数组
 * normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
 * normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
 * @param {Array|Object} map
 * @return {Object}
 */
function normalizeMap (map) {
  if (!isValidMap(map)) {
    return []
  }
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

module.contextЭто задание в основном даетсяhelpersсерединаmapState,mapGetters,mapMutations,mapActionsИспользуются четыре вспомогательные функции.

// 在构造函数中 installModule 中
const local = module.context = makeLocalContext(store, namespace, path)

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

getModuleByNamespace

function getModuleByNamespace (store, helper, namespace) {
  // _modulesNamespaceMap 这个变量在 class Store installModule 函数中赋值的
  const module = store._modulesNamespaceMap[namespace]
  if (process.env.NODE_ENV !== 'production' && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
}

Прочитав их, наконец, приведите пример:vuex/examples/shopping-cart/components/ShoppingCart.vue

computed: {
    ...mapState({
      checkoutStatus: state => state.cart.checkoutStatus
    }),
}

Без пространства имен он в конечном итоге будет преобразован в это

computed: {
    checkoutStatus: this.$store.state.checkoutStatus
}

Предполагая, что существует пространство имен «ruochuan»,

computed: {
    ...mapState('ruochuan', {
      checkoutStatus: state => state.cart.checkoutStatus
    }),
}

преобразуется в:

computed: {
    checkoutStatus: this.$store._modulesNamespaceMap.['ruochuan/'].context.checkoutStatus
}

mapGetters

Создайте вычисляемое свойство для возвращаемого компонента.getterВозвращаемое значение.

export const mapGetters = normalizeNamespace((namespace, getters) => {
  const res = {}
  // 省略代码:非生产环境 判断参数 getters 必须是数组或者是对象
  normalizeMap(getters).forEach(({ key, val }) => {
    // The namespace has been mutated by normalizeNamespace
    val = namespace + val
    res[key] = function mappedGetter () {
      if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
        return
      }
      // 省略代码:匹配不到 getter
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

Пример:

computed: {
  ...mapGetters('cart', {
    products: 'cartProducts',
    total: 'cartTotalPrice'
  })
},

что, наконец, переводится как:

computed: {
  products: this.$store.getters['cart/cartProducts'],
  total: this.$store.getters['cart/cartTotalPrice'],
}

mapActions

Создать диспетчеризацию метода компонентаaction.

export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
  // 省略代码: 非生产环境 判断参数 actions  必须是数组或者是对象
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction (...args) {
      // get dispatch function from store
      let dispatch = this.$store.dispatch
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
        if (!module) {
          return
        }
        dispatch = module.context.dispatch
      }
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

mapMutations

Создание представления метода компонентаmutation. mapMutations похож на mapActions, за исключением того, что диспетчеризация заменена фиксацией.

let commit = this.$store.commit
commit = module.context.commit
return typeof val === 'function'
        ? val.apply(this, [commit].concat(args))
        : commit.apply(this.$store, [val].concat(args))

vuex/src/helpers

mapMutations,mapActionsПример:

{
  methods: {
    ...mapMutations(['inc']),
    ...mapMutations('ruochuan', ['dec']),
    ...mapActions(['actionA'])
    ...mapActions('ruochuan', ['actionB'])
  }
}

наконец-то превратился в

{
  methods: {
    inc(...args){
      return this.$store.dispatch.apply(this.$store, ['inc'].concat(args))
    },
    dec(...args){
      return this.$store._modulesNamespaceMap.['ruochuan/'].context.dispatch.apply(this.$store, ['dec'].concat(args))
    },
    actionA(...args){
      return this.$store.commit.apply(this.$store, ['actionA'].concat(args))
    }
    actionB(...args){
      return this.$store._modulesNamespaceMap.['ruochuan/'].context.commit.apply(this.$store, ['actionB'].concat(args))
    }
  }
}

Видно, что эти вспомогательные функции значительно облегчают работу разработчика.

createNamespacedHelpers

Создайте вспомогательные функции привязки компонентов на основе пространства имен.

export const createNamespacedHelpers = (namespace) => ({
  // bind(null) 严格模式下,napState等的函数 this 指向就是 null
  mapState: mapState.bind(null, namespace),
  mapGetters: mapGetters.bind(null, namespace),
  mapMutations: mapMutations.bind(null, namespace),
  mapActions: mapActions.bind(null, namespace)
})

Это должно поместить эти вспомогательные функции в объект.

плагин

Путь к файлу части плагина:
vuex/src/plugins/devtool
vuex/src/plugins/logger

Статья относительно длинная, поэтому эту часть описывать не будем. За подробностями обращайтесь на склад автораvuex-analysis vuex/src/plugins/комментарии к исходному коду.

Суммировать

В статье более подробно описаноvuex,vueМетоды отладки исходного кода иVuexпринцип. и подробныйVuex.useустановить иnew Vuex.Storeинициализация,Vuex.StoreвсеAPI(Такие какdispatch,commitи т. д.) реализация и вспомогательные функцииmapState,mapGetters,mapActions,mapMutations createNamespacedHelpers.

Примечания к статье, вvuex-analysisВ основном на складе исходного кода есть анализ аннотаций, попросите егоstar. Опять же, настоятельно рекомендуется клонировать код.

git clone https://github.com/lxchuan12/vuex-analysis.git

первыйStoreРаспечатайте экземпляр, просмотрите конкретную структуру, а затем выполните отладку с помощью точек останова экземпляра, которые могут сделать больше с меньшими затратами.

VuexИсходный код относительно небольшой, более тысячи строк после упаковки, его очень стоит изучить, и он легче читается.

Если читатель обнаружит что-то неправильное или что-то, что можно улучшить, или где это неясно написано, пожалуйста, прокомментируйте и укажите. Кроме того, я чувствую, что написание хорошее, и это немного полезно для вас.Вы можете ставить лайки, комментировать, пересылать и делиться, что также является своего рода поддержкой для автора.Большое спасибо.

Рекомендуемое чтение

официальная документация vuex
репозиторий vuex на гитхабе
Meituan Mingxi: принцип структуры Vuex и анализ исходного кодаЭта статья настоятельно рекомендуется, блок-схема хорошо нарисована
Знание Хуан И: анализ исходного кода Vuex 2.0Эта статья также настоятельно рекомендуется, она более полная
Bugs Cancer: анализ исходного кода Vuex (практика чтения исходного кода)Эта статья также настоятельно рекомендуется, в основном о том, как читать исходный код.
Ран Мо: Анализ исходного кода Vuex
Команда внешнего интерфейса Netease koala: анализ исходного кода Vuex
yck: Углубленный анализ исходного кода Vuex.
Xiaosheng Fangqin: [Front-end Dictionary] Интерпретация процесса внедрения Vuex в жизненный цикл Vue из исходного кода

Очередная серия автора

Интервьюер спросил: наследование JS
Интервьюер спросил: этот пункт JS
Интервьюер спросил: Могу ли я смоделировать вызов и применить методы JS?
Интервьюер спросил: Могу ли я смоделировать метод привязки, реализующий JS?
Интервьюер спросил: Могу ли я смоделировать новый оператор, реализующий JS?

о

Автор: Чанг ИВакагаваНазвание смешано в реках и озерах. По дороге на фронт | Энтузиасты РРТ | Знаю очень мало, только хорошо учусь.
Личный блог-Вакагава,использоватьvuepressРефакторинг, опыт чтения может быть лучше
Колонка самородков, добро пожаловать, обратите внимание~
segmentfaultПередняя колонка обзора, добро пожаловать, обратите внимание~
Знайте переднюю колонку видения, добро пожаловать, обратите внимание~
github blog, соответствующий исходный код и ресурсы размещены здесь, попросите одинstar^_^~

Добро пожаловать, чтобы добавить общедоступную учетную запись WeChat для общения в WeChat

Может быть более интересным общедоступный аккаунт WeChat, нажмите и удерживайте, чтобы отсканировать код, чтобы следовать. Вы также можете добавить WeChatruochuan12, укажите источник и втяните вас в [Front-end Vision Exchange Group].

若川视野
Видение Вакагава

В этой статье используетсяmdniceнабор текста