Содержание этой статьи взято из проекта InterviewMap с открытым исходным кодом. Текущее содержание проекта включает в себя JS, сеть, браузер, апплет, оптимизацию производительности, безопасность, фреймворк, Git, структуру данных, алгоритм и т. д. Будь то базовый или расширенный, или интерпретация исходного кода, вы можете найти его в я получил удовлетворительный ответ в карте, и я надеюсь, что эта карта интервью поможет всем лучше подготовиться к интервью.
Vuex идея
Прежде чем интерпретировать исходный код, давайте кратко разберемся в идее Vuex.
Vuex поддерживает объект глобально и использует шаблон проектирования singleton. В этом глобальном объекте все свойства являются адаптивными, и любые изменения свойств вызывают обновление компонентов, использующих это свойство. и только черезcommit
Способ изменения состояния для достижения режима одностороннего потока данных.
Разбор Vuex
установка vuex
Прежде чем читать следующий контент, рекомендуется локально клонировать копию исходного кода Vuex для облегчения понимания.
Прежде чем использовать Vuex, нам всем нужно вызватьVue.use(Vuex)
. вызовuse
Во время процесса Vue будет обращаться к Vuex.install
функция
install
Функция очень проста
- Убедитесь, что Vuex установлен только один раз
- смешать
beforeCreate
Хук-функция, которую можно использовать в компонентеthis.$store
export function install (_Vue) {
// 确保 Vuex 只安装一次
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
// applyMixin
export default function (Vue) {
// 获得 Vue 版本号
const version = Number(Vue.version.split('.')[0])
// Vue 2.0 以上会混入 beforeCreate 函数
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// ...
}
// 作用很简单,就是能让我们在组件中
// 使用到 this.$store
function vuexInit () {
const options = this.$options
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
}
}
}
Инициализация Vuex
this._modules
В этом разделе в основном анализируется, как инициализироватьthis._modules
export class Store {
constructor (options = {}) {
// 引入 Vue 的方式,自动安装
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
// 在开发环境中断言
if (process.env.NODE_ENV !== 'production') {
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.`)
}
// 获取 options 中的属性
const {
plugins = [],
strict = false
} = options
// store 内部的状态,重点关注 this._modules
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
const store = this
const { dispatch, commit } = this
// bind 以下两个函数上 this 上
// 便于 this.$store.dispatch
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)
}
}
см. далееthis._modules
процесс, возьмите следующий код в качестве примера
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
state: { ... },
modules: {
a: moduleA,
b: moduleB
}
})
Для приведенного выше кодаstore
можно рассматривать какroot
. При первом исполнении,rootModule
, а потом судитьroot
существует вmodules
свойства, затем рекурсивно зарегистрируйтеmodule
. Для ребенка это будетparent
, затем вparent
добавлено вmodule
.
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
register (path, rawModule, runtime = true) {
// 开发环境断言
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule)
}
// 初始化 Module
const newModule = new Module(rawModule, runtime)
// 对于第一次初始化 ModuleCollection 时
// 会走第一个 if 条件,因为当前是 root
if (path.length === 0) {
this.root = newModule
} else {
// 获取当前 Module 的 parent
const parent = this.get(path.slice(0, -1))
// 添加 child,第一个参数是
// 当前 Module 的 key 值
parent.addChild(path[path.length - 1], newModule)
}
// 递归注册
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
}
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
// 用于存储 children
this._children = Object.create(null)
// 用于存储原始的 rawModule
this._rawModule = rawModule
const rawState = rawModule.state
// 用于存储 state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
}
installModule
см. далееinstallModule
реализация
// installModule(this, state, [], this._modules.root)
function installModule (store, rootState, path, module, hot) {
// 判断是否为 rootModule
const isRoot = !path.length
// 获取 namespace,root 没有 namespace
// 对于 modules: {a: moduleA} 来说
// namespace = 'a/'
const namespace = store._modules.getNamespace(path)
// 为 namespace 缓存 module
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
// 设置 state
if (!isRoot && !hot) {
// 以下逻辑就是给 store.state 添加属性
// 根据模块添加
// state: { xxx: 1, a: {...}, b: {...} }
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}
// 该方法其实是在重写 dispatch 和 commit 函数
// 你是否有疑问模块中的 dispatch 和 commit
// 是如何找到对应模块中的函数的
// 假如模块 A 中有一个名为 add 的 mutation
// 通过 makeLocalContext 函数,会将 add 变成
// a/add,这样就可以找到模块 A 中对应函数了
const local = module.context = makeLocalContext(store, namespace, path)
// 以下几个函数遍历,都是在
// 注册模块中的 mutation、action 和 getter
// 假如模块 A 中有名为 add 的 mutation 函数
// 在注册过程中会变成 a/add
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 这里会生成一个 _wrappedGetters 属性
// 用于缓存 getter,便于下次使用
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 递归安装模块
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
resetStoreVM
см. далееresetStoreVM
, это свойство реализует отзывчивость состояния и будет_wrappedGetters
так какcomputed
Атрибуты.
// resetStoreVM(this, state)
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// 设置 getters 属性
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
// 遍历 _wrappedGetters 属性
forEachValue(wrappedGetters, (fn, key) => {
// 给 computed 对象添加属性
computed[key] = () => fn(store)
// 重写 get 方法
// store.getters.xx 其实是访问了
// store._vm[xx]
// 也就是 computed 中的属性
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// 使用 Vue 来保存 state 树
// 同时也让 state 变成响应式
const silent = Vue.config.silent
Vue.config.silent = true
// 当访问 store.state 时
// 其实是访问了 store._vm._data.?state
store._vm = new Vue({
data: {
?state: state
},
computed
})
Vue.config.silent = silent
// 确保只能通过 commit 的方式改变状态
if (store.strict) {
enableStrictMode(store)
}
}
Общий API
совершить синтаксический анализ
Если вам нужно изменить состояние, обычно используетсяcommit
Давайте сделаем это, давайте посмотримcommit
как изменить состояние
commit(_type, _payload, _options) {
// 检查传入的参数
const { type, payload, options } = unifyObjectStyle(
_type,
_payload,
_options
)
const mutation = { type, payload }
// 找到对应的 mutation 函数
const entry = this._mutations[type]
// 判断是否找到
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// _withCommit 函数将 _committing
// 设置为 TRUE,保证在 strict 模式下
// 只能 commit 改变状态
this._withCommit(() => {
entry.forEach(function commitIterator(handler) {
// entry.push(function wrappedMutationHandler(payload) {
// handler.call(store, local.state, payload)
// })
// handle 就是 wrappedMutationHandler 函数
// wrappedMutationHandler 内部就是调用
// 对于的 mutation 函数
handler(payload)
})
})
// 执行订阅函数
this._subscribers.forEach(sub => sub(mutation, this.state))
}
Диспетчерский анализ
Если вам нужно изменить состояние асинхронно, вам нужно сделать это по диспетчеризации. позвонил в диспетчерскуюcommit
Все функции переопределены и найдут функцию мутации в модуле.
dispatch(_type, _payload) {
// 检查传入的参数
const { type, payload } = unifyObjectStyle(_type, _payload)
const action = { type, payload }
// 找到对于的 action 函数
const entry = this._actions[type]
// 判断是否找到
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
// 触发订阅函数
this._actionSubscribers.forEach(sub => sub(action, this.state))
// 在注册 action 的时候,会将函数返回值
// 处理成 promise,当 promise 全部
// resolve 后,就会执行 Promise.all
// 里的函数
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
различный синтаксический сахар
В компонентах, если вы хотите нормально использовать функции Vuex, вам часто нужно вызывать вот такthis.$store.state.xxx
путь, доставляющий массу неудобств. С этой целью Vuex вводит функцию синтаксического сахара, которая позволяет нам простым способом реализовать вышеуказанные функции. со следующимmapState
Например, другие карты имеют схожие принципы, поэтому они не будут анализироваться по отдельности.
function normalizeNamespace(fn) {
return (namespace, map) => {
// 函数作用很简单
// 根据参数生成 namespace
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
return fn(namespace, map)
}
}
// 执行 mapState 就是执行
// normalizeNamespace 返回的函数
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
// 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 } ]
// function normalizeMap(map) {
// return Array.isArray(map)
// ? map.map(key => ({ key, val: key }))
// : Object.keys(map).map(key => ({ key, val: map[key] }))
// }
// states 参数可以参入数组或者对象类型
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState() {
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
// 获得对应的模块
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
state = module.context.state
getters = module.context.getters
}
// 返回 State
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
наконец
Выше приведен анализ исходного кода Vue.Хотя общий код Vuex невелик, это проект, который стоит прочитать. Если у вас возникнут вопросы во время прочтения или вы найдете мои ошибки, пожалуйста, обсудите в комментариях.
Если вы хотите узнать больше о интерфейсе, навыках прохождения собеседований или некоторых моих личных мыслях, вы можете подписаться на мой официальный аккаунт, чтобы учиться вместе.