This article is an excerpt from the team’s open source project, InterviewMap. Project currently content contains the JS, network, browser, small procedures, performance optimization, security, framework, Git, data structures and algorithms, both basic and advanced, or source code, you can get a satisfactory answer in this map, map hope this interview can help to better prepare for the interview.
Vuex thought
Before we dive into the source code, let’s take a quick look at the idea behind Vuex.
Vuex globally maintains an object, using the singleton design pattern. In this global object, all properties are reactive, and changes to any property cause the component that uses the property to be updated. In addition, the state can only be changed through the commit mode, which implements the unidirectional data flow mode.
Vuex parsing
Vuex installation
Before looking at the next content, it is recommended that local Clone a Vuex source code contrast to see, easy to understand.
Before using Vuex, we all need to call vue.use (Vuex). During the use call, Vue calls Vuex’s install function
The install function is simple
- Make sure that Vuex is installed only once
- Mixed with
beforeCreate
A hook function that can be used in a componentthis.$store
export function install (_Vue) {
// Make sure that Vuex is installed only once
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) {
// Obtain the Vue version number
const version = Number(Vue.version.split('. ') [0])
BeforeCreate = beforeCreate = beforeCreate
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// ...
}
// The function is simple, is to let us in the component
// 使用到 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
}
}
}
Copy the code
Vuex initialization
this._modules
How to initialize this._modules
export class Store {
constructor (options = {}) {
// Introduce Vue mode, automatic installation
if(! Vue &&typeof window! = ='undefined' && window.Vue) {
install(window.Vue)
}
// Assert in the development environment
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.`)}// Get the properties in options
const {
plugins = [],
strict = false
} = options
// Store internal state, focusing on 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 the following two functions to this
/ / to facilitate 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)
}
}
Copy the code
Next look at the procedure for this._modules, using the following code as an example
const moduleA = {
state: {... },mutations: {... },actions: {... },getters: {... }}const moduleB = {
state: {... },mutations: {... },actions: {... }}const store = new Vuex.Store({
state: {... },modules: {
a: moduleA,
b: moduleB
}
})
Copy the code
For the above code, store can be thought of as root. On the first execution, it initializes a rootModule, determines if there are modules properties in root, and recursively registers the Module. For a child, it gets its parent and adds a Module to the parent.
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
register (path, rawModule, runtime = true) {
// Development environment assertion
if(process.env.NODE_ENV ! = ='production') {
assertRawModule(path, rawModule)
}
// Initialize the Module
const newModule = new Module(rawModule, runtime)
// When ModuleCollection is initialized the first time
// The first if condition is invoked, because root is currently in place
if (path.length === 0) {
this.root = newModule
} else {
// Get the parent of the current Module
const parent = this.get(path.slice(0.- 1))
// Add child as the first argument
// The key of the current Module
parent.addChild(path[path.length - 1], newModule)
}
// Register recursively
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
// To store children
this._children = Object.create(null)
// Used to store raw RawModules
this._rawModule = rawModule
const rawState = rawModule.state
// To store state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
}
Copy the code
installModule
Next, look at the installModule implementation
// installModule(this, state, [], this._modules.root)
function installModule (store, rootState, path, module, hot) {
// Check whether it is a rootModule
constisRoot = ! path.lengthRoot does not have a namespace
// Modules: {a: moduleA
// namespace = 'a/'
const namespace = store._modules.getNamespace(path)
// Cache module for namespace
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
/ / set the state
if(! isRoot && ! hot) {// The following logic is to add attributes to store.state
// Add by module
// state: { xxx: 1, a: {... }, b: {... }}
const parentState = getNestedState(rootState, path.slice(0.- 1))
const moduleName = path[path.length - 1]
store._withCommit((a)= > {
Vue.set(parentState, moduleName, module.state)
})
}
// This method overwrites the dispatch and commit functions
// Do you have questions about dispatch and commit in the module
// How to find the function in the corresponding module
// Suppose there is A mutation named add in module A
// With the makeLocalContext function, add becomes
// a/add, so you can find the corresponding function in module A
const local = module.context = makeLocalContext(store, namespace, path)
// The following functions are traversed in
// Register the mutation, action and getter in the module
// Suppose the mutation function named add is in module A
// This will change to a/add during the registration process
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)
})
// This generates a _wrappedGetters attribute
// To cache getters for the next time
module.forEachGetter((getter, key) = > {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// Install modules recursively
module.forEachChild((child, key) = > {
installModule(store, rootState, path.concat(key), child, hot)
})
}
Copy the code
resetStoreVM
Next, look at the implementation of resetStoreVM, which implements state responsiveness and uses _wrappedGetters as a computed attribute.
// resetStoreVM(this, state)
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// Set the getters property
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
// Run through the _wrappedGetters attribute
forEachValue(wrappedGetters, (fn, key) => {
// Add attributes for a computed object
computed[key] = (a)= > fn(store)
// Override the get method
// Store.getters
// store._vm[xx]
// That is, computed
Object.defineProperty(store.getters, key, {
get: (a)= > store._vm[key],
enumerable: true // for local getters})})// Use Vue to save the state tree
// Also make state reactive
const silent = Vue.config.silent
Vue.config.silent = true
// When accessing store.state
// Store._vm. _data.? state
store._vm = new Vue({
data: {
? state: state
},
computed
})
Vue.config.silent = silent
// Ensure that the state can only be changed by committing
if (store.strict) {
enableStrictMode(store)
}
}
Copy the code
Commonly used API
Commit parsing
If you need to change the state, you usually use a COMMIT. Let’s look at how a COMMIT can change the state
commit(_type, _payload, _options) {
// Check the parameters passed in
const { type, payload, options } = unifyObjectStyle(
_type,
_payload,
_options
)
const mutation = { type, payload }
// Find the corresponding mutation function
const entry = this._mutations[type]
// Check whether it was found
if(! entry) {if(process.env.NODE_ENV ! = ='production') {
console.error(`[vuex] unknown mutation type: ${type}`)}return
}
// The _withCommit function will do _research
// Set this parameter to TRUE to ensure that the system is in strict mode
// Only commit can change the state
this._withCommit((a)= > {
entry.forEach(function commitIterator(handler) {
// entry.push(function wrappedMutationHandler(payload) {
// handler.call(store, local.state, payload)
// })
// Handle is the wrappedMutationHandler function
// wrappedMutationHandler is called internally
// For the mutation function
handler(payload)
})
})
// Execute the subscription function
this._subscribers.forEach(sub= > sub(mutation, this.state))
}
Copy the code
Dispatch parsing
If you need to change the state asynchronously, you need to do it by dispatch. Any commit function called at Dispatch is overwritten, and the mutation function in the module is found.
dispatch(_type, _payload) {
// Check the parameters passed in
const { type, payload } = unifyObjectStyle(_type, _payload)
const action = { type, payload }
// Find the action function for
const entry = this._actions[type]
// Check whether it was found
if(! entry) {if(process.env.NODE_ENV ! = ='production') {
console.error(`[vuex] unknown action type: ${type}`)}return
}
// Trigger the subscription function
this._actionSubscribers.forEach(sub= > sub(action, this.state))
// When the action is registered, the function returns a value
// When the promise is all
// After resolve, promise.all is executed
//
return entry.length > 1
? Promise.all(entry.map(handler= > handler(payload)))
: entry[0](payload)
}
Copy the code
Grammatical sugars
In component, if you want to use Vuex function normally, you often need to call this.$store.state. XXX, which causes a lot of inconvenience. To this end, Vuex has introduced the syntactic sugar feature, which allows us to do this in a simple way. In the following example, mapState is used as an example. Other maps have similar principles and will not be resolved one by one.
function normalizeNamespace(fn) {
return (namespace, map) = > {
// The function is simple
// Generate a namespace based on parameters
if (typeofnamespace ! = ='string') {
map = namespace
namespace = ' '
} else if (namespace.charAt(namespace.length - 1)! = ='/') {
namespace += '/'
}
return fn(namespace, map)
}
}
// Executing mapState is executing
// Function returned by 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 can be used as an array or object type
normalizeMap(states).forEach(({ key, val }) = > {
res[key] = function mappedState() {
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
// Get the corresponding module
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
state = module.context.state
getters = module.context.getters
}
/ / return to the State
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
Copy the code
The last
Above is Vue source code parsing, although the overall code of Vuex is not much, but it is a project worth reading. If you have any questions or mistakes along the way, feel free to discuss them in the comments.
If you want to learn more front-end knowledge, interview skills or some of my personal insights, you can follow my public number to learn together