Navigation
[[ thorough 01] Execution context ](https://juejin.im/post/684490...)
[[ thorough 02] Prototype chain ](https://juejin.im/post/684490...)
[[ thorough 03] Inherit ](https://juejin.im/post/684490...)
[[ thorough 04] The event loop ](https://juejin.im/post/684490...)
[[ thorough 05] currying Partial function Function memory ](https://juejin.im/post/684490...)
[[ thorough 06] Implicit conversion and Operator ](https://juejin.im/post/684490...)
[[ thorough 07] Browser caching mechanism (http Caching mechanisms )](https://juejin.im/post/684490...)
[[ thorough 08] Front end security ](https://juejin.im/post/684490...)
[[ thorough 09] Depth copy ](https://juejin.im/post/684490...)
[[ thorough 10] Debounce Throttle](https://juejin.im/post/684490...)
[[ thorough 11] Front-end routing ](https://juejin.im/post/684490...)
[[ thorough 12] Front-end modularization ](https://juejin.im/post/684490...)
[[ thorough 13] Observer mode Publish subscribe mode Two way data binding ](https://juejin.im/post/684490...)
[[ thorough 14] canvas](https://juejin.im/post/684490...)
[[ thorough 15] webSocket](https://juejin.im/post/684490...)
[[ thorough 16] webpack](https://juejin.im/post/684490...)
[[ thorough 17] http and https](https://juejin.im/post/684490...)
[[ thorough 18] CSS-interview](https://juejin.im/post/684490...)
[[ thorough 19] Handwriting Promise](https://juejin.im/post/684490...)
[[ thorough 20] Handwritten functions ](https://juejin.im/post/684490...)
[[react] Hooks](https://juejin.im/post/684490...)
[[ Deploy 01] Nginx](https://juejin.im/post/684490...)
[[ Deploy 02] Docker Deploy vue project ](https://juejin.im/post/684490...)
[[ Deploy 03] gitlab-CI](https://juejin.im/post/684490...)
[[ Source code -webpack01- Pre knowledge ] AST Abstract syntax tree ](https://juejin.im/post/684490...)
[[ Source code -webpack02- Pre knowledge ] Tapable](https://juejin.im/post/684490...)
[[ Source code -webpack03] Handwriting webpack - compiler Simple compilation process ](https://juejin.im/post/684490...)
[[ Source code ] Redux React-Redux01](https://juejin.im/post/684490...)
[[ Source code ] axios ](https://juejin.im/post/684490...)
[[ Source code ] vuex ](https://juejin.im/post/684490...)
[[ Source code -vue01] data Response type and Initialize rendering ](https://juejin.im/post/684490...)
[[ Source code -vue02] computed Response type - initialization , visit , The update process ](https://juejin.im/post/684490...)
[[ Source code -vue03] watch Listening properties - Initialization and update ](https://juejin.im/post/684490...)
[[ Source code -vue04] Vue.set and vm.$set ](https://juejin.im/post/684490...)
[[ Source code -vue05] Vue.extend ](https://juejin.im/post/684490...)
[[ Source code -vue06] Vue.nextTick and vm.$nextTick ](https://juejin.im/post/684790...)
Pre knowledge
Some words
Mutation: variation
raw: Unprocessed
assert: Assertion
internal: Inside
( store internal state : store Inside state )
duplicate: repeat , assignment , copy
localized: Local
unify: Unified
( unifyObjectStyle Unified object type )
Valid: Effective
vue How to write a plug-in
Plug ins are usually used to vue add to ( Global capabilities )
- The global method , Global resources , The whole thing is in , vue Example method etc.
- namely
Vue Constructor static methods and properties
,Vue.prototype Instance methods and properties
,mixin
,directive
etc.
If using plug-ins
By component form :
-
Vue.use(plugin, [options])
-
new Vue( Component options )
-
adopt script Mode introduction
such as
vue-router plug-in unit
- Will be in index.js Internal judgment If it is ( Browser environment and window.Vue There is ), Call it automatically window.Vue.use(VueRouter)
Be careful :
<font color=red> Each plug-in should expose a install Method , also install The first parameter of the method must be Vue Constructors , The second parameter is an optional parameter object </font>
- because install The first parameter of the method is Vue Constructors , So you can get Vue Upper directive, mixin, wait
- You'll know later if it's not exposed install Method , The plug-in itself must be a function , This function will be treated as install Method
Code
plug-in unit : const firstPlugin = { install(Vue, options) { console.log(options, 'options') // The global method firstGlobalMethod Vue.firstGlobalMethod = function () { console.log(' plug-in unit - The global method - namely Vue Static method of constructor ') } // Add global resources Global instructions Vue.directive('first-directive', { bind(el, binding, vnode, oldVnode) { console.log(' Call it once , The first time an instruction is bound to an element . Here you can do one-time initialization settings ') console.log(el, 'el Element bound by instruction , Can be used for direct operation DOM') console.log(binding, 'binding') console.log(vnode, 'Vue Compile generated virtual nodes ') console.log(oldVnode, ' Previous virtual node , Only in update and componentUpdated Available in hook ') }, inserted() { console.log(' Called when the bound element is inserted into the parent node ( Only the parent node is guaranteed to exist , But it doesn't have to be inserted into the document )') } }) // Global mix Injection component options Vue.mixin({ created: function () { console.log(' plug-in unit - Global mix - Mix in all components created() Life cycle ') } }) // Example method // The general rule is Vue.prototype The properties and methods mounted on the must be in the form of $ start Vue.prototype.$firstPluginMethod = function () { console.log(' plug-in unit - Example method - Mounted on vue Method on instance prototype object , Remember that the property and method are represented by $ start ') } } } export default firstPlugin
Register plug-ins : import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import axios from 'axios' import firstFlugin from './plugin/first-plugin' Vue.config.productionTip = false Vue.prototype.$axios = axios Vue.use(firstFlugin) // -------------------------------------------------------------- Register plug-ins new Vue({ router, store, render: h => h(App) }).$mount('#app')
Vue.use()
- Vue.use( plugin )
- Parameters :object perhaps function
effect :
- install Vue plug-in unit
- Plug ins are objects : Need to provide install Method
- Plug ins are functions : They will act as install Method
- install Method : Will Vue As a parameter , stay Vue.use() Automatically execute on call
Be careful :
- <font color=red>Vue.use() Need to be in new Vue() Called before </font>
- When install Method is called multiple times by the same plug-in , The plug-in will only be installed once
Vue.use() Source code
I have mainly done the following things
If the plug-in has been registered
- Go straight back to ( return this Mainly to chain call )
Not registered
Determine whether the plug-in is an object or a function
- <font color=red> object : call install Method </font>
- <font color=red> function : Use the plug-in as a install Method , Call this function directly </font>
- Add the plug-in to the plug-in array , Judge whether the registration is outdated next time , Just registered
- return this Convenient chain call
summary :
- Vue.use() The most important thing is to call the plug-in install Method
// Vue.use() Source code // File path :core/global-api/use.js import { toArray } from '../util/index' export function initUse(Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { // Vue.use stay Vue Mount on constructor use Method const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // installedPlugins // If Vue._installedPlugins There is a direct assignment // If Vue._installedPlugins There is no empty array assigned if (installedPlugins.indexOf(plugin) > -1) { // The plug-in is in the plug-in array installedPlugins If it exists in, it returns directly // Return here this, You can realize chain call , there this Namely Vue return this } const args = toArray(arguments, 1) // additional parameters // Will remove Vue Other parameters are integrated into the array args.unshift(this) // take Vue Add to the front of the array // because args To be a plug-in install Method parameters , and install Method specifies that the first argument must be Vue Constructors if (typeof plugin.install === 'function') { // Plug ins are objects , Just put the plug-in object install Methodical this Bind to this plugin On , Pass in parameters to execute plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // Plug ins are functions , Reference execution plugin.apply(null, args) } installedPlugins.push(plugin) // If the plug-in is in installedPlugins non-existent , First assign an empty array to it , perform isntall after , Add the plug-in to installedPlugins Indicates presence return this // return this For chained calls ,this Refer to Vue } } // ------------------------------------------------------ // toArray(list, start) // such as : // list = [1,2,3,4] // start = 1 export function toArray(list: any, start?: number): Array<any> { start = start || 0 let i = list.length - start const ret: Array<any> = new Array(i) while (i--) { ret[i] = list[i + start] // i-- Assign value first, i.e while(3) // Then subtract 1 namely i=2 // ret[2] = list[3] // ret[1] = list[2] // ret[0] = list[1] // ret = [2,3,4] } return ret }
vuex Source code
(1) vuex How to install and use
install
- npm install vuex -S
Use
main.js in ---- import Vuex from 'vuex' Vue.use(Vuex) // -------------------------------- Would call Vuex Upper install Method const store = new Vuex.Store({ // --------------- new Store(options) state: {}, mutations: {}, actions: {}, getters: {}, modules: {} }) const app = new Vue({ router, store, // -------------------------------------- Inject store render: h => h(App) }).$mount('#app')
(2) Vue.use(Vuex)
Vue.use(Vuex) Would call Vuex Medium Vuex.install Method
<font color=red>Vuex.install</font> Method will call <font color=red>applyMixin(Vue)</font>
applyMixin(Vue)
- The main thing is to put store Real examples are injected into each component
Specifically, in the... Of the component beforeCreate In the life cycle mixin Mix in <font color=red>vuexInit</font> Method , So that store Instance injection into each component
mixin We need to pay attention to :
-
- Hook functions with the same name will be merged into an array , So all will be called
-
- The hook of the object will be called before the component itself hooks
-
summary :
Vue.use(Vuex) => Vuex.install() => applyMixin(Vue) => Vue.mixin({ beforeCreate: vuexInit })
- adopt Vue.use(Vuex) The ultimate effect is to store Instances are injected into each component , And share the same store example
- therefore : You can use... In each component this.$store Access to the strore example
Vuex Medium install Method ---- let Vue // bind on install export function install (_Vue) { if (Vue && _Vue === Vue) { if (__DEV__) { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return // Vue There is , And with the incoming _Vue equal , And the production environment , return } Vue = _Vue // Vue non-existent , Just assign the parameters passed in _Vue applyMixin(Vue) // call applyMixin Method }
applyMixin(Vue)
---
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
// Version greater than or equal to 2, Mix in beforeCreate Lifecycle hook vuexInit Method
} else {
// edition 1 It's for compatibility , Don't consider
}
function vuexInit () {
const options = this.$options
// there this Represents each component , have beforeCreate hook
// store injection
if (options.store) {
// this.$options.store There is
// 1. Description is the root component , That is, through const vue = new Vue({store: store}) Generated instances vue, That's the root component
this.$store = typeof options.store === 'function'
? options.store()
: options.store
// On the root component, change to $store attribute
// 1. store Is a function , Call function directly , Return value assignment
// 2. store It's an object , Just assign it directly
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
// If it's not the root component , And the parent component exists
// 1. because :( Parent component beforeCreate ) Will be earlier than ( Subcomponents beforeCreate ) Execute first , In addition to the root component , Of the remaining components $store All use parent components $store
// 2. therefore : Resulting in... Of all components $store It's all used Of the root component $store, The root component uses only its own $store, That is, layer by layer transmission
// 3. Final : All components use Of the root component $store , That is, they all use the incoming store example
}
}
}
(3) Store Class constructor
<font color=blue size=5>this._modules = new ModuleCollection(options)</font>
ModuleCollection class
The main role
-
- assignment this.root Is the root module
-
- If... Exists in the module modules attribute , To the parent module _children Add the corresponding relationship of the module in the attribute object
-
Instance attributes
root
- namely new Module example
Prototype attribute
- getNamespace: If module Of namespaced Attributes exist , Recursive splicing path
- Call in constructor this.register([], rawRootModule, false)
<font color=blue>this.register([], rawRootModule, false)</font>
- The main function is :
<font color=blue>new Module(rawModule, runtime)</font>
Instance attributes
- Mount on instance <font color=blue>runtime, _children, _rawModule, state</font>
_children: The default is an empty object
- It's used to store the seeds module
_rawModule: It's the incoming module
- Or roots module The incoming Vuex Of options
- Or modules The individual module
state
- Is a function, call the function to return state object
- Is a direct assignment to an object
Prototype attribute
- On Prototype <font color=blue>getChild,addChild,namespaced,update,forEachGetter ...</font>
- getChild: return _children Object module
- addChild: towards _children Object module
- namespaced: The module Whether they have namespaced attribute , A Boolean value
- forEachGetter: loop getters object , take value and key Pass to parameter function
<font color=blue>parent.addChild(path[path.length - 1], newModule)</font>
- To the father module Medium _children Object to add a submodule mapping
<font color=blue>array.prototype.reduce The method needs attention </font>
[].reduce((a,b) => {...}, params)
When an empty array is executed reduce when , Regardless of the first argument, the function performs any logic , Will directly return the second parameter params- In the source code, there are a large number of empty arrays to execute reduce The logic of , Such as registration module And register namespce
<font color=blue>array.prototype.slice The method needs attention </font>
[].slice(0, -1)
It returns an empty array
<font color=red size=5>installModule(this, state, [], this._modules.root)</font>
The main role
- take module After splicing ( path ) and ( moudle) As key-value Mapping to ( store._modulesNamespaceMap ) In the object
state
- Will all modules Medium module Medium state, Merge into rootModule Of state in
- key => moduleName
- value => moduleState
- What needs to be noticed is the modification state All need to pass ( store._withCommit ) packing
local
- structure module.context object , Assign a value to local object , take local The object is passed in as a parameter registerMutation,registerAction,registerGetter
- local There are dispatch, commit, getters, state Equal attribute
mutations
Will all module Medium ( mutations The function in ) All added to store Of ( this._mutations ) In the object
key:
- The module Of namespace + mutations The name of a function in
- such as
cart/incrementItemQuantity
namelymodule name + mutation Function name
value
- An array
- Members are specific mutation Wrapper function of function ,
wrappedMutationHandler (payload)
Be careful :
<table><tr><td bgcolor=orange>mutations hander</td></tr></table>
Parameters
The first parameter state
- The state Is the name of the current local module state, Not the total state
- The second parameter payload
<table><tr><td bgcolor=yellow>commit</td></tr></table>
- store.commit
- modify store in state The only way is
store.commit(mutations hander)
store.commit('increment', 10)
store.commit('increment', {amount: 10})
store.commit({type: 'increment',amount: 10})
actions
- Will all module Medium ( actions The function in ) All added to store Of ( this._actions ) In the object
key:
- action.root ? key : namespace + key
- above key It means actions Medium action Function name
- above namespace It means moudle name +/
- such as :
cart/addProductToCart
namelymodule name + action Function name
value:
- An array
- Members are specific action Wrapper function of function ,
wrappedActionHandler(payload)
Be careful :
<table><tr><td bgcolor=orange>action function </td></tr></table>
Parameters
The first parameter
- It's a context object , context and store Instances have the same methods and properties
- context Object has
dispatch commit getters state rootGetters rootState
These attributes
The second parameter
- payload
<table><tr><td bgcolor=yellow>dispatch</td></tr></table>
- store.dispatch
- action Function is passed store.dispatch To trigger
store.dispatch('increment')
store.dispatch('incrementAsync', {amount: 10})
store.dispatch({type: 'incrementAsync', amount: 10})
getters
Will all module Medium getters All mapped to ( store._wrappedGetters ) On the object
- key:namespace + key
module name /getter function
- value: A function , namely wrappedGetter (store)
- key:namespace + key
<table><tr><td bgcolor=orange>getter function </td></tr></table>
Parameters
- The first parameter : Local state object
- The second parameter : Local getters object
- The third parameter : root state
- Fourth parameter : root getters
(state, getters, rootState, rootGetters) => {...}
- Be careful getter The parameters of the function are in order , and action The function is the first argument. The object has no order
- loop moudle Medium ( _children ) object , Execute sequentially ( installModule ) Method
<font color=red>store._modules.getNamespace(path)</font>
- The main role : Splicing module Of path, And then assign it to namespace
<font color=red>store._withCommit(fn)</font>
Package the incoming mutation function
- stay mutation When the function executes, the this._committing Set up true
- stay mutation After the function is executed, the this._committing Set up false
- This ensures that the changes state Only through mutation function , If you modify it directly, it is not set this._committing by true, It proves not to pass mutation In the revision
<font color=red>Vue.set(parentState, moduleName, module.state)</font>
- take moudles Medium module Medium state Merge to parent state On
<font color=red>makeLocalContext(store, namespace, path)</font>
- Generate local object , have dispatch, commit, getters, state Equal attribute
<font color=red>module.forEachMutation(fn)</font>
- Loop through the current module Medium mutations object , take value and key Pass in fn(mutation, key)
- Splicing namespace and key
- call registerMutation(store, namespacedType, mutation, local)
<font color=red>registerMutation(store, namespacedType, mutation, local)</font>
towards ( store._mutations[type] ) Array push One ( mutation Wrapper functions )
store._mutations[type]
type
yesnamespace/getter The function name of the function
, Like thiscart/incrementItemQuantity
value
yeswrappedMutationHandler (payload)
A function like this
- The wrapper function is to mutation Function to add a shell , Pass in payload, Call inside again mutation handle function
Be careful :
- store._mutations It's an object
- Every store._mutations[type] Is an array
<font color=red>module.forEachAction(fn)</font>
- and
module.forEachMutation(fn)
similar Loop through the current module Medium actions object
- use ( action.root ) To get a different type
- use ( action.handle ) Whether there is a value to be assigned handle
- call
registerAction(store, type, handler, local)
function
- and
<font color=red>registerAction(store, type, handler, local)</font>
- towards ( store._actions[type] ) Array push One ( action Wrapper functions wrappedActionHandler )
<font color=red>wrappedActionHandler(payload)</font>
- Will be called internally ( action perhaps action.handler ) The function is named handler function
- If handler The return value of the function is not promise, The return value is converted to promise object , and resolve, Then return fulfilled State of promise object
<font color=red>handler()</font>
- binding this To store
Parameters :
The first parameter is an object , Has the following properties
- dispatch
- commit
- getters
- state
- rootGetters
- rootState
- The second parameter is payload
<font color=red>module.forEachGetter(fn)</font>
- Loop traversal module Medium getters All in getter
- take value and key Pass to fn(value, key)
<font color=red>registerGetter(store, namespacedType, getter, local)</font>
to ( store._wrappedGetters ) Add attribute method
- key => namespace + key
- value => wrappedGetter
<font color=red>wrappedGetter</font>
- getter The wrapper function of ,
(store) => getter(localState, localGetters, rootState, rootGetters)
- Parameters : Local state getters, root state getter
- getter The wrapper function of ,
<font color=DarkOrchid>commit (_type, _payload, _options) </font>
I have mainly done the following things :
If the first parameter is an object , Modification parameters
- ( One ) store.commit('increment', {amount: 10})
- ( Two ) store.commit({type: 'increment',amount: 10})
- Transform the second into the first
Look for the mutaation, That is to say this._mutations Pass through key seek mutation Corresponding [mutation]
- No error found
- Find the... Stored in the circular array mutation Wrapper functions , It will call the real mutation handler function
- Shallow copy this._subscribers Then iterate over the array , Call the subscribe function => Change state After that, you need to respond to the view, etc
subscribe
subscribe(handler: Function): Function
store.subscribe((mutation, state) => {...})
- subscribe store Of mutation
- handler Will be in every mutation After completion, call , receive mutation And what happened mutation After the state as a parameter
- To stop the subscription , Call the function returned by this method to stop the subscription
<font color=DarkOrchid>dispatch (_type, _payload) </font>
I have mainly done the following things
- Modification parameters
perform this._actionSubscribers Medium before Crocheted action Monitor function
- before Indicates that the call time of the subscription processing function should be in a action Call before distribution
perform action function
- this._actions[type] The array length is greater than 1, Just promise.all packing , Guarantee result It's all promise all resolve
- this._actions[type] Array length is less than or equal to 1, Call directly
When all promise all resolve after , That is, after all asynchronies return results , perform _actionSubscribers in after Crocheted action Monitor function
- after Indicates that the call time of the subscription processing function should be in a action After distribution, call
- And the final result resolve get out , return promise object
subscribeAction
subscribeAction(handler: Function): Function
store.subscribeAction((action, state) => {...})
- subscribe store Of action
- handler Will be in every action Called and received at the time of distribution action Description and current store Of state These two parameters
Be careful :
- from 3.1.0 rise ,subscribeAction You can also specify that the call time of the subscription processing function should be in a action Before or after distribution ( The default behavior is before )
- You can specify
store.subscribeAction({before: (action, state) => {},after: (action, state) => {}})
Source code
(1) this._modules = new ModuleCollection(options)
The main role
- assignment this.root Is the root module
- If... Exists in the module modules attribute , To the parent module _children Add the corresponding relationship of the module in the attribute object
// File directory src/module/module-collection.js
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
}
---
register (path, rawModule, runtime = true) {
if (__DEV__) {
assertRawModule(path, rawModule)
// dev Environmental Science , Assert incoming options Each of the attribute objects in key For corresponding value The type of , If it's not what it should be , Just throw it wrong
}
// dev Environment and incoming options Meet assertion requirements perhaps prod Environmental Science
// Then enter the following code
const newModule = new Module(rawModule, runtime) // ---------------------------------------- analysis 1
// Create a module example
// module
// Instance attributes runtime, _children, _rawModule, state
// Prototype attribute getChild,addChild,namespaced,update,forEachGetter ... etc.
if (path.length === 0) {
this.root = newModule
// The length of the array is 0, Just new Module Instance assigned to root attribute
} else {
// problem : When path.length !== 0
// answer : The following will determine whether there is modules attribute , When it exists modules Attribute , Will call... Again register, At this time, the length is not 0
const parent = this.get(path.slice(0, -1)) // -------------------------------------------- analysis 2
// parent: Get parent module
parent.addChild(path[path.length - 1], newModule) // ------------------------------------- analysis 3
// To the father module._children Add to object key and value, The father modlue The son of module
}
if (rawModule.modules) {
// If it's time to module in modules object , Will traverse modules
forEachValue(rawModule.modules, (rawChildModule, key) => { // --------------------------- analysis 4
this.register(path.concat(key), rawChildModule, runtime)
// path.concat(key) => similar [module1],[module2], Be careful concat Will not change path
// rawChildModule => module1 module2
})
}
}
analysis 1
// File directory src/module/module.js export default class Module { constructor (rawModule, runtime) { this.runtime = runtime // Store some children item this._children = Object.create(null) // _children object // For storage modules All children in the property moudle // key => moudle Name // value => module object , There could be state,mutations,actions,getters etc. // Store the origin module object which passed by programmer this._rawModule = rawModule // The current module const rawState = rawModule.state // Store the origin module's state this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} // state // function : Just call , return stated object // object : Direct assignment } addChild (key, module) { this._children[key] = module } getChild (key) { return this._children[key] } }
analysis 2
get (path) { // When path When is an empty array ,[].reducer((a,b) => a.getChild(key), this.root) return return root return path.reduce((module, key) => { return module.getChild(key) }, this.root) }
analysis 3
addChild (key, module) { this._children[key] = module // to module Example of _children Add a mapping to the property object // key => moudle name // value => module object }
analysis 4
export function forEachValue (obj, fn) { Object.keys(obj).forEach(key => fn(obj[key], key)) // Traverse obj // fn(value, key) // take obj Medium key As the second parameter // take obj Medium value As the first parameter }
(2) Store Class constructor
Store Class constructor - src/stroe.js
---
export class Store {
constructor (options = {}) {
// Auto install if it is not done yet and `window` has `Vue`.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
// If Vue non-existent also window There is also window.Vue There is , Install automatically
}
if (__DEV__) {
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.`)
// Assertion
// Vue.use(Vuex) Must be in new Vuex.store() Pre invocation
// Whether there is promise, because vuex rely on promise
// Store Must pass new Command invocation
}
const {
plugins = [], // Deconstruct assignment plugins and strict, And assign default value
strict = false
} = options
// store internal state
this._committing = false
// _committing
// Sign a , The default is false
// modify state Can use _withCommit Function packing , In the implementation of the modification state Function ,this._committing=true, Set as after modification false
// Used to determine whether to pass mutation Function modification state, Only through mutation modify state It's legal
// For example, in the merger state It will be used in the operation of
this._actions = Object.create(null)
// _actions
// Store all moudle Of action
// {
// cart/addProductToCart: [ƒ], f refer to wrappedActionHandler (payload)
// cart/checkout: [ƒ],
// products/getAllProducts: [],
// }
this._actionSubscribers = []
// _actionSubscribers
// collect action Monitor function
// Pay attention to distinguish between :
// this._subscribers = [] mutation Monitor function
this._mutations = Object.create(null)
// _mutations
// Store all moudle Of mutation
// {
// cart/incrementItemQuantity: [ƒ], f refer to wrappedMutationHandler
// cart/pushProductToCart: [ƒ],
// cart/setCartItems: [ƒ],
// cart/setCheckoutStatus: [ƒ],
// products/decrementProductInventory: [ƒ],
// products/setProducts: [ƒ],
// }
this._wrappedGetters = Object.create(null)
// _wrappedGetters
// Store all module Medium getter
// {
// cart/cartProducts: ƒ wrappedGetter(store)
// cart/cartTotalPrice: ƒ wrappedGetter(store)
// }
// Be careful :!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// 1. this._wrappedGetters stay resetStoreVM(this, state) use
// 2. Pay attention to distinguish between ( store.getters ) and ( store._wrappedGetters)
this._modules = new ModuleCollection(options)
// _modules
// ModuleCollection Class is mainly to collect all moudle
// {
// root: {
// runtime: false,
// state: {}, // Note that there is no merge at this time state
// _children: { // hold moudules Medium module Placed in parent module _children Properties of the
// cart: Module,
// products: Module,
// },
// _rawModule: root module
// }
// }
this._modulesNamespaceMap = Object.create(null)
// _modulesNamespaceMap
// namespace and mdule One to one mapping object
// {
// cart/: Module
// products/: Module
// }
this._subscribers = []
// _subscribers
// mutation Monitor function
// Pay attention to distinguish between :
// this._actionSubscribers = [] action Monitor function
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
// 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)
// binding dispatch Functional this To store For instance
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
// binding commit Functional this To store For instance
}
// strict mode
this.strict = strict
// Strict mode
// The default is flase
// In strict mode , Only through mutation modify state
// In a production environment, it is recommended to shut down , Because strict mode will deeply monitor the status tree to detect non-conforming status changes , There is a loss of performance
const state = this._modules.root.state
// 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) // --------------------------------- The following analysis
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state) // ----------------------------------------------------------- The following analysis
// apply plugins
plugins.forEach(plugin => plugin(this))
// Loop traversal plug-in , Pass in stroe
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
// Whether there is : Pass in new Vuex.stroe(options) Medium options in devtools
// There is :options.devtools
// non-existent :Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
}
(3) installModule(this, state, [], this._modules.root)
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
// When path The length of the array is 0, It's roots module
const namespace = store._modules.getNamespace(path)
// obtain namespace => module name + /
// => perhaps ''
// register in namespace map
if (module.namespaced) {
// module.namespaced
// Every module There can be namespaced attribute , It's a Boolean value
// Indicates that the local is turned on module name
// Namespace official website introduction (https://vuex.vuejs.org/zh/guide/modules.html)
if (store._modulesNamespaceMap[namespace] && __DEV__) {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
// repeated
}
store._modulesNamespaceMap[namespace] = module
// _modulesNamespaceMap
// establish module and nameSpace The mapping relation of
// key : namespace
// vlaue: module
// {
// cart/: Module
// products/: Module
// }
}
// set state
if (!isRoot && !hot) {
// Not a root module also hot by flase, Will enter judgment
const parentState = getNestedState(rootState, path.slice(0, -1))
// parentState
// Gets the name of the module
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
Vue.set(parentState, moduleName, module.state)
// Merge all modules Medium state To rootState
})
}
const local = module.context = makeLocalContext(store, namespace, path)
// Statement module.context And the assignment
// local
// dispatch
// commit
// getters
// state
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
// Put all the modules Each of them mutations In the mutation Function added to _mutations On the object
// _mutations The object has the following format
// {
// cart/incrementItemQuantity: [ƒ], f refer to wrappedMutationHandler
// cart/pushProductToCart: [ƒ],
// cart/setCartItems: [ƒ],
// cart/setCheckoutStatus: [ƒ],
// products/setProducts: [f],
// }
})
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
// Put all the module Each of them actions In the action Function added to _actions On the object
// _actions The object has the following format
// {
// cart/addProductToCart: [ƒ], f refer to wrappedActionHandler (payload)
// cart/checkout: [ƒ]
// products/getAllProducts: []
// }
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
// Put all the modul Each of them getter Function added to _wrappedGetters On the object
// Store all module Medium getter
// {
// cart/cartProducts: ƒ wrappedGetter(store)
// cart/cartTotalPrice: ƒ wrappedGetter(store)
// }
})
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
// Loop traversal module._children object , And execute... In each cycle installModule Method
})
}
(4) resetStoreVM(this, this.state, hot)
function resetStoreVM (store, state, hot) {
// resetStoreVM
// Parameters
// store
// state
// hot
const oldVm = store._vm
// oldVm Cache old store._vm
// bind store public getters
store.getters = {}
// stay store Add... To the instance getters Property object , The initial value is an empty object
// Be careful :
// 1. distinguish store._wrappedGetters and store.getters
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
// wrappedGetters
// cache store._wrappedGetters
const computed = {}
// Statement computed Variable
forEachValue(wrappedGetters, (fn, key) => {
// loop wrappedGetters, take value and key Pass in as a parameter forEachValue The second parameter function of
computed[key] = partial(fn, store)
// 1. partial It's such a function
// function partial (fn, arg) {
// return function () {
// return fn(arg)
// }
// }
// 2. namely computed[key] = () => fn(store)
// fn It's concrete getter function
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // enumerable
})
// 1. to store.getters Object to add key attribute
// 2. visit stroe.getters[key] = store._vm[key]
// By visiting store.getter.aaaa It's equivalent to visiting store._vm.aaaa
})
// 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
// cache Vue.config.silent
Vue.config.silent = true
// Turn on cancel warning
// Cancel Vue All the logs and warnings , That is to say new Vue() There will be no warning when
store._vm = new Vue({
data: {
?state: state // 11. Will the incoming state Assign a value to data Medium ?state attribute
},
computed // 22. take computed Change wolf assigned to Vue Medium computed attribute (computed[key] = () => fn(store))
})
// store._vm = new Vue(...)
// Go over it 1122 bring state and () => fn(store) Responsive
Vue.config.silent = silent
// Turn off cancel warning
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
// Enable strict mode , Guarantee to modify store Only through mutation
}
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.?state = null
// Remove references
})
}
Vue.nextTick(() => oldVm.$destroy())
// dom After the update , destroy vue example
// const oldVm = store._vm
// store._vm = new Vue()
}
}
(5) commit
commit (_type, _payload, _options) {
// check object-style commit
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
// structure commit Required parameter type
// 1. store.commit('increment', 10)
// 2. store.commit('increment', {amount: 10})
// 3. store.commit({type: 'increment',amount: 10})
// That's to put the first 3 The second case is constructed as 2 Two kinds of situations
const mutation = { type, payload }
const entry = this._mutations[type]
// entry Find what needs to be submitted mutation Function , Is an array , An array is a wrapped mutation handle function
if (!entry) {
// I didn't find the mutation
if (__DEV__) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
// this._withCommit Guarantee to modify state It's a legal means , namely this._committing Modifying yes yes true
entry.forEach(function commitIterator (handler) {
handler(payload)
// Pass in parameters to execute mutation handle function
})
})
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))
// Shallow copy this._subscribers Then iterate over the array , Call the subscribe function
// Change state After that, you need to respond to the view, etc
if (
__DEV__ &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
(6) dispatch
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
// structure commit Required parameter type
// 1. store.dispatch('increment')
// 2. store.dispatch('incrementAsync', {amount: 10})
// 3. store.dispatch({type: 'incrementAsync', amount: 10})
// That's to put the first 3 The second case is constructed as 2 Two kinds of situations
const action = { type, payload }
const entry = this._actions[type]
if (!entry) {
// I didn't find the action
if (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
// before hook action Monitor function
// before Indicates that the call time of the subscription processing function should be in a action Call before distribution
} catch (e) {
if (__DEV__) {
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)
// Longer than 1,promise.all() Guarantee result all resolve
// Length less than or equal to 1, Call directly
return new Promise((resolve, reject) => {
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
// after Indicates that the call time of the subscription processing function should be in a action After distribution, call
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
// resolve final result
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
(7) mapstate
First of all mapstate How to use
Examples of the official website computed: { ...mapState('some/nested/module', { a: state => state.a, b: state => state.b }) } When the name of the mapped calculated property is the same as state The names of the child nodes of are the same , We can also give mapState Pass an array of strings computed: mapState([ // mapping this.count by store.state.count 'count' ])
Source code
- hold state Constructed as computed Object returns
- according to namespace hold ( Local state and getter As a parameter ) Passed to the passed in parameter object ( Property function )
export const mapState = normalizeNamespace((namespace, states) => { // normalizeNamespace // Return the modified parameters ,f(namespace, states) // Change to the following parameter form // ...mapState('some/nested/module', { // a: state => state.a, // b: state => state.b // }) const res = {} if (__DEV__ && !isValidMap(states)) { // If it is dev Environmental Science also states Not an object or an array // Report errors // function isValidMap (map) { // return Array.isArray(map) || isObject(map) // } console.error('[vuex] mapState: mapper parameter must be either an Array or an Object') } normalizeMap(states).forEach(({ key, val }) => { // 1. If states It's an array , Returns an array , Each member is an object ({ key: key, val: key }) // 2. If states It's the object , Returns an array , Each member is an object ({ key:key, val: map[key] }) res[key] = function mappedState () { let state = this.$store.state let getters = this.$store.getters if (namespace) { const module = getModuleByNamespace(this.$store, 'mapState', namespace) // module // stay ( store._modulesNamespaceMap ) Find... In the object ( Parameters namespace ) Corresponding ( module ) if (!module) { return } state = module.context.state // Get the module Local of species state getters = module.context.getters // Get the module Local of species getters } return typeof val === 'function' ? val.call(this, state, getters) : state[val] // val It's a function , Just call the function val.call(this, state, getters) return // val It's not a function , Go straight back state[val] } // mark vuex getter for devtools res[key].vuex = true }) return res // Finally back to res object // res Object will be used as a component computed })
------
function normalizeNamespace (fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
// If namespace Not a string
// Description: only one parameter was passed in
// Just put namespace='', Assign the first parameter that is not a string to the second parameter
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
// No, / Then add
}
return fn(namespace, map)
// Returns the value after the conversion parameter fn
}
}
------
function getModuleByNamespace (store, helper, namespace) {
// 1. getModuleByNamespace(this.$store, 'mapState', namespace)
const module = store._modulesNamespaceMap[namespace]
// find namespace Corresponding module
if (__DEV__ && !module) {
console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
// return module
}
------
function normalizeMap (map) {
if (!isValidMap(map)) {
return []
// Not an array or object , Returns an array by default
}
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
// 1. If it's an array , Returns an array , Each member is an object ({ key: key, val: key })
// 2. If it's an object , Returns an array , Each member is an object ({ key:key, val: map[key] })
}
Precautions in use
mapState - ( belt namespace And without namespace)
mapGetters
mapMutations
mapActions
ui In the component
<template> <div class="vuex"> <div> <div style="font-size: 30px">vuex</div> <div>dispatch One action => store.dispatch({type: 'actionName', payload: ''})</div> <div>commit One mutation => store.dispatch({type: 'actionName', payload: ''})</div> <div>------</div> <button @click="changeCount"> Click Modify vuexModule Medium count+1 </button> <div>{{moduleState.count}}</div> <div>------</div> <div><button @click="getName"> Click on , Initiate request , obtain name data , utilize Vuex actions - Don't pass parameters </button></div> <div><button @click="getName2"> Click on , Initiate request , obtain name data , utilize Vuex actions - The ginseng </button></div> <div>{{moduleState.name}}</div> </div> </div> </template> <script> import { mapState, mapGetters, mapMutations, mapActions } from "vuex"; export default { name: "vuex", data() { return {}; }, computed: { ...mapState({ rootState: state => { // --------------- Name it rootState return state; // ----------------------- there state yes rootMoudule Of state } }), ...mapState("vuexModule", { // ------------ namespace moduleState: state => { // -------------- Name it moduleState return state; // ---------------------- there state yes moudles in vuexModule Of state } }), ...mapGetters("vuexModule", { // ---------- The second parameter is the object , That is, you can modify getter Name changeGetterName: "square" }), ...mapGetters("vuexModule", ['square']), // The second parameter is the array }, methods: { ...mapMutations('vuexModule', { addCountChangeName: 'AddCount' // ------- Object mode , You can modify mutation Name }), ...mapActions('vuexModule', ['getData', 'getData2']), // mapActions changeCount() { this.addCountChangeName(1) // ----------- Parameters will be used as mutation(state, payload) Of payload }, getName() { this.getData() // ----------------------- Do not pass to action function , Deal with asynchronous }, getName2() { this.getData2({ // ----------------------- Pass the reference to action function , Deal with asynchronous url: "/home", method: "get" }) } }, mounted() { console.log( this.rootState.vuexModule.count, " No, namespace Of mapState Acquired state - visit coount" ); console.log( this.moduleState.count, " have namespace Of mapState Acquired state - visit count" ); console.log(this.changeGetterName, 'mapGetters The second parameter is the object '); console.log(this.square, 'mapGetters The second parameter is the array '); } }; </script>
store/vuexModule
import {getName} from '../../api/home' const vuex = { namespaced: true, state() { return { count: 2, name: 'init name' } }, getters: { square(state, getters, rootState, rootGetters) { console.log(state, 'state') console.log(getters, 'getters') console.log(rootState, 'rootState') console.log(rootGetters, 'rootGetters') return (state.count * rootState.rootCount) } }, mutations: { AddCount(state, payload) { state.count = state.count + payload }, getNameData(state, payload) { state.name = payload } }, actions: { async getData(context) { // dispatch Don't wear ginseng action function const {commit} = context const res = await getName({ url: "/home", method: "get" }); if (res && res.data && res.data.name) { console.log(res.data.name, '2222') commit('getNameData', res.data.name) } }, async getData2(context, payload) { // dispatch Wear ginseng to action function // action(context, payload) // The first parameter : context and store Have the same api // The second parameter :payload yes dispatch One action Pass through parameters const {commit} = context const res = await getName(payload); // const res = await getName({ // url: "/home", // method: "get" // }); if (res && res.data && res.data.name) { console.log(res.data.name, '2222') commit('getNameData', res.data.name) } }, } }; export default vuex
Information
vuex Official document https://vuex.vuejs.org/zh/
Chuan Shen More comprehensive https://juejin.im/post/684490...
2.3.1 edition Vuex, The process is detailed :https://juejin.im/post/684490...
yck https://juejin.im/post/684490...