共计 31632 个字符,预计需要花费 80 分钟才能阅读完成。
导航
[[深刻 01] 执行上下文 ](https://juejin.im/post/684490…)
[[深刻 02] 原型链 ](https://juejin.im/post/684490…)
[[深刻 03] 继承 ](https://juejin.im/post/684490…)
[[深刻 04] 事件循环 ](https://juejin.im/post/684490…)
[[深刻 05] 柯里化 偏函数 函数记忆 ](https://juejin.im/post/684490…)
[[深刻 06] 隐式转换 和 运算符 ](https://juejin.im/post/684490…)
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…)
[[深刻 08] 前端平安 ](https://juejin.im/post/684490…)
[[深刻 09] 深浅拷贝 ](https://juejin.im/post/684490…)
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…)
[[深刻 11] 前端路由 ](https://juejin.im/post/684490…)
[[深刻 12] 前端模块化 ](https://juejin.im/post/684490…)
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定 ](https://juejin.im/post/684490…)
[[深刻 14] canvas](https://juejin.im/post/684490…)
[[深刻 15] webSocket](https://juejin.im/post/684490…)
[[深刻 16] webpack](https://juejin.im/post/684490…)
[[深刻 17] http 和 https](https://juejin.im/post/684490…)
[[深刻 18] CSS-interview](https://juejin.im/post/684490…)
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…)
[[深刻 20] 手写函数 ](https://juejin.im/post/684490…)
[[react] Hooks](https://juejin.im/post/684490…)
[[部署 01] Nginx](https://juejin.im/post/684490…)
[[部署 02] Docker 部署 vue 我的项目 ](https://juejin.im/post/684490…)
[[部署 03] gitlab-CI](https://juejin.im/post/684490…)
[[源码 -webpack01- 前置常识] AST 形象语法树 ](https://juejin.im/post/684490…)
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…)
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程 ](https://juejin.im/post/684490…)
[[源码] Redux React-Redux01](https://juejin.im/post/684490…)
[[源码] axios ](https://juejin.im/post/684490…)
[[源码] vuex ](https://juejin.im/post/684490…)
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…)
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…)
[[源码 -vue03] watch 侦听属性 – 初始化和更新 ](https://juejin.im/post/684490…)
[[源码 -vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490…)
[[源码 -vue05] Vue.extend ](https://juejin.im/post/684490…)
[[源码 -vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790…)
前置常识
一些单词
Mutation:变异
raw:未加工
assert: 断言
internal:外部的
(store internal state : store 外部的 state)
duplicate:反复,赋值,正本
localized:部分的
unify:对立
(unifyObjectStyle 对立对象类型)
Valid: 无效的
vue 如何编写一个插件
-
插件通常来为 vue 增加 (全局性能)
- 全局办法,全局资源,全局混入,vue 实例办法 等
- 即
Vue 结构器静态方法和属性
,Vue.prototype 实例办法和属性
,mixin
,directive
等
-
如果应用插件
-
通过组件模式:
-
Vue.use(plugin, [options])
-
new Vue(组件选项)
-
-
通过 script 形式引入
-
比方
vue-router 插件
- 会在 index.js 外部判断 如果是 (浏览器环境并且 window.Vue 存在),就主动调用 window.Vue.use(VueRouter)
-
-
-
留神点:
-
<font color=red> 每个插件都应该裸露一个 install 办法,并且 install 办法的第一个参数必须是 Vue 结构器,第二个参数是一个可选的参数对象 </font>
- 因为 install 办法的第一个参数是 Vue 结构器,所以能够获取 Vue 上的 directive, mixin, 等等
- 前面会晓得如果没有裸露 install 办法,那插件自身就必须是一个函数,该函数就会被当做 install 办法
-
-
代码
插件:const firstPlugin = {install(Vue, options) {console.log(options, 'options') // 全局办法 firstGlobalMethod Vue.firstGlobalMethod = function () {console.log('插件 - 全局办法 - 即 Vue 结构器的静态方法') } // 增加全局资源 全局指令 Vue.directive('first-directive', {bind(el, binding, vnode, oldVnode) {console.log('只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置') console.log(el, 'el 指令所绑定的元素,能够用来间接操作 DOM') console.log(binding, 'binding') console.log(vnode, 'Vue 编译生成的虚构节点') console.log(oldVnode, '上一个虚构节点,仅在 update 和 componentUpdated 钩子中可用') }, inserted() {console.log('被绑定元素插入父节点时调用 ( 仅保障父节点存在,但不肯定已被插入文档中)') } }) // 全局混合 注入组件选项 Vue.mixin({created: function () {console.log('插件 - 全局混合 - 在所有组件中混入 created() 生命周期') } }) // 实例办法 // 个别规定在 Vue.prototype 上挂载的属性和办法都要以 $ 结尾 Vue.prototype.$firstPluginMethod = function () {console.log('插件 - 实例办法 - 挂载在 vue 实例原型对象上的办法,记得该属性和办法以 $ 结尾') } } } export default firstPlugin
注册插件: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) // -------------------------------------------------------------- 注册插件 new Vue({ router, store, render: h => h(App) }).$mount('#app')
Vue.use()
- Vue.use(plugin)
- 参数:object 或者 function
-
作用:
- 装置 Vue 插件
- 插件是对象:需提供 install 办法
- 插件是函数:本身会作为 install 办法
- install 办法:会将 Vue 作为参数,在 Vue.use() 调用时主动执行
-
留神:
- <font color=red>Vue.use() 须要在 new Vue() 之前被调用 </font>
- 当 install 办法被同一个插件屡次调用,插件将只会被装置一次
-
Vue.use() 源码
-
次要做了以下事件
-
如果插件注册过
- 间接返回 (返回 this 次要为了能链式调用)
-
没注册过
-
判断该插件是对象还是函数
- <font color=red> 对象:调用 install 办法 </font>
- <font color=red> 函数:把该插件作为 install 办法,间接调用该函数 </font>
- 把该插件增加进插件数组,下次判断是否注册过期,就注册过了
- 返回 this 不便链式调用
-
-
总结:
- Vue.use() 最次要就是调用插件的 install 办法
-
// Vue.use() 源码 // 文件门路:core/global-api/use.js import {toArray} from '../util/index' export function initUse(Vue: GlobalAPI) {Vue.use = function (plugin: Function | Object) { // Vue.use 在 Vue 结构器上挂载 use 办法 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // installedPlugins // 如果 Vue._installedPlugins 存在就间接赋值 // 如果 Vue._installedPlugins 不存在赋值为空数组 if (installedPlugins.indexOf(plugin) > -1) { // 该插件在插件数组 installedPlugins 中存在就间接返回 // 这里返回 this,就能够实现链式调用,这里的 this 就是 Vue return this } const args = toArray(arguments, 1) // additional parameters // 将除去 Vue 以外的参数收集成数组 args.unshift(this) // 将 Vue 增加到数组的最后面 // 因为 args 要作为插件 install 办法的参数,而 install 办法规定第一个参数必须是 Vue 结构器 if (typeof plugin.install === 'function') { // 插件是对象,就把插件对象的 install 办法的 this 绑定到该 plugin 上,传入参数执行 plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // 插件是函数,传参执行 plugin.apply(null, args) } installedPlugins.push(plugin) // 如果该插件在 installedPlugins 不存在,首先在下面赋值为空数组,执行 isntall 后,把该插件增加进 installedPlugins 示意存在 return this // return this 是为了链式调用,this 指代 Vue } } // ------------------------------------------------------ // toArray(list, start) // 比方:// 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-- 先赋值即 while(3) // 而后减去 1 即 i =2 // ret[2] = list[3] // ret[1] = list[2] // ret[0] = list[1] // ret = [2,3,4] } return ret }
-
vuex 源码
(1) vuex 如何装置和应用
-
装置
- npm install vuex -S
-
应用
main.js 中 ---- import Vuex from 'vuex' Vue.use(Vuex) // -------------------------------- 会调用 Vuex 上的 install 办法 const store = new Vuex.Store({// --------------- new Store(options) state: {}, mutations: {}, actions: {}, getters: {}, modules: {}}) const app = new Vue({ router, store, // -------------------------------------- 注入 store render: h => h(App) }).$mount('#app')
(2) Vue.use(Vuex)
-
Vue.use(Vuex) 会调用 Vuex 中的 Vuex.install 办法
-
<font color=red>Vuex.install</font> 办法会调用 <font color=red>applyMixin(Vue)</font>
-
applyMixin(Vue)
- 次要就是把 store 实例子注入每个组件中
-
具体就是在组件的的 beforeCreate 生命周期中 mixin 混入 <font color=red>vuexInit</font> 办法,从而把 store 实例注入每个组件
-
mixin 须要留神:
-
- 同名钩子函数将合并为一个数组,因而都将被调用
-
- 混入对象的钩子将在组件本身钩子之前调用
-
-
-
-
-
总结:
Vue.use(Vuex) => Vuex.install() => applyMixin(Vue) => Vue.mixin({beforeCreate: vuexInit})
- 通过 Vue.use(Vuex) 的最终成果就是将 store 实例注入到每个组件中,且是共用同一个 store 实例
- 所以:能够在每个组件中通过 this.$store 拜访到 strore 实例
Vuex 中的 install 办法 ---- 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 存在,并且和传入的_Vue 相等,并且是生产环境,返回 } Vue = _Vue // Vue 不存在,就赋值传入的参数_Vue applyMixin(Vue) // 调用 applyMixin 办法 }
applyMixin(Vue)
---
export default function (Vue) {const version = Number(Vue.version.split('.')[0])
if (version >= 2) {Vue.mixin({ beforeCreate: vuexInit})
// 版本大于等于 2,混入 beforeCreate 生命周期钩子 vuexInit 办法
} else {// 版本 1 是为了兼容,不思考}
function vuexInit () {
const options = this.$options
// 这里的 this 代表每一个组件,具备 beforeCreate 钩子
// store injection
if (options.store) {
// this.$options.store 存在
// 1. 阐明是根组件,即通过 const vue = new Vue({store: store}) 生成的实例 vue,即根组件
this.$store = typeof options.store === 'function'
? options.store()
: options.store
// 在根组件上改在 $store 属性
// 1. store 是个函数,间接调用函数,返回值赋值
// 2. store 是个对象,就间接赋值
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
// 如果不是根组件,并且父组件存在
// 1. 因为:(父组件的 beforeCreate) 会早于 (子组件的 beforeCreate) 先执行,而除了根组件,其余组件的 $store 都是应用父组件的 $store
// 2. 所以:导致所有组件的 $store 都是应用的 根组件的 $store,根组件只用本身的 $store,即一层层传递
// 3. 最终:所有组件都应用了 根组件的 $store,即都应用了传入的 store 实例
}
}
}
(3) Store 类的 constructor
<font color=blue size=5>this._modules = new ModuleCollection(options)</font>
-
ModuleCollection 类
-
次要作用
-
- 赋值 this.root 为根模块
-
- 如果模块中存在 modules 属性,就给父模块的 _children 属性对象中增加该模块的对应关系
-
-
实例属性
-
root
- 即 new Module 实例
-
-
原型属性
- getNamespace:如果 module 的 namespaced 属性存在,就递归的拼接 path
- 在构造函数中调用 this.register([], rawRootModule, false)
-
<font color=blue>this.register([], rawRootModule, false)</font>
- 次要作用就是:
-
<font color=blue>new Module(rawModule, runtime)</font>
-
实例属性
- 实例上挂载 <font color=blue>runtime, _children, _rawModule, state</font>
-
_children:默认是一个空对象
- 用来寄存子 module
-
_rawModule:就是传入的 module
- 要么是根 module 即传入 Vuex 的 options
- 要么是 modules 中的各个 module
-
state
- 是一个函数就调用该函数返回 state 对象
- 是一个对象间接赋值
-
原型属性
- 原型上具备 <font color=blue>getChild,addChild,namespaced,update,forEachGetter …</font>
- getChild:返回_children 对象中的某个 module
- addChild:向_children 对象中增加莫格 module
- namespaced:该 module 是否具备 namespaced 属性,一个布尔值
- forEachGetter:循环 getters 对象,将 value 和 key 传给参数函数
-
-
<font color=blue>parent.addChild(path[path.length – 1], newModule)</font>
- 向父 module 中的_children 对象中增加子模块映射
-
<font color=blue>array.prototype.reduce 办法须要留神 </font>
[].reduce((a,b) => {...}, params)
当空数组执行 reduce 时,不论第一个参数函数执行了任何逻辑,都会间接返回第二个参数 params- 在源码中有大量的空数组执行 reduce 的逻辑,比方注册 module 和注册 namespce
-
<font color=blue>array.prototype.slice 办法须要留神 </font>
[].slice(0, -1)
返回的是一个空数组
<font color=red size=5>installModule(this, state, [], this._modules.root)</font>
-
-
次要作用
- 将 module 的拼接后的 (path) 和 (moudle) 作为 key-value 映射到 (store._modulesNamespaceMap) 对象中
-
state
- 将所有 modules 中的 module 中的 state,合并到 rootModule 的 state 中
- key => moduleName
- value => moduleState
- 须要留神的是批改 state 都须要通过 (store._withCommit) 包装
-
local
- 结构 module.context 对象,赋值给 local 对象,将 local 对象作为参数传入 registerMutation,registerAction,registerGetter
- local 对象上有 dispatch, commit, getters, state 等属性
-
mutations
-
将所有的 module 中的 (mutations 中的函数) 都增加到 store 的 (this._mutations) 对象中
-
key:
- 该 module 的 namespace + mutations 中的某个函数名
- 比方
cart/incrementItemQuantity
即module 名 + mutation 函数的名字
-
value
- 一个数组
- 成员是具体的 mutation 函数的包装函数,
wrappedMutationHandler (payload)
-
-
留神:
-
<table><tr><td bgcolor=orange>mutations hander</td></tr></table>
-
参数
-
第一个参数 state
- 该 state 是以后部分模块的 state,而不是总的 state
- 第二个参数 payload
-
-
-
<table><tr><td bgcolor=yellow>commit</td></tr></table>
- store.commit
- 批改 store 中 state 的惟一形式是
store.commit(mutations hander)
store.commit('increment', 10)
store.commit('increment', {amount: 10})
store.commit({type: 'increment',amount: 10})
-
-
-
actions
- 将所有的 module 中的 (actions 中的函数) 都增加到 store 的 (this._actions) 对象中
-
key:
- action.root ? key : namespace + key
- 下面的 key 示意的是 actions 中的 action 函数名
- 下面的 namespace 示意的 moudle 名 +/
- 比方:
cart/addProductToCart
即module 名 + action 函数的名字
-
value:
- 一个数组
- 成员是具体的 action 函数的包装函数,
wrappedActionHandler(payload)
-
留神:
-
<table><tr><td bgcolor=orange>action 函数 </td></tr></table>
-
参数
-
第一个参数
- 是一个 context 对象,context 和 store 实例具备雷同办法和属性
- context 对象具备
dispatch commit getters state rootGetters rootState
这些属性
-
第二个参数
- payload
-
-
-
<table><tr><td bgcolor=yellow>dispatch</td></tr></table>
- store.dispatch
- action 函数是通过 store.dispatch 来触发的
store.dispatch('increment')
store.dispatch('incrementAsync', {amount: 10})
store.dispatch({type: 'incrementAsync', amount: 10})
-
-
getters
-
将所有 module 中的 getters 都映射到 (store._wrappedGetters) 对象上
- key:namespace + key
module 名 /getter 函数
- value: 一个函数,即 wrappedGetter (store)
- key:namespace + key
-
<table><tr><td bgcolor=orange>getter 函数 </td></tr></table>
-
参数
- 第一个参数:部分 state 对象
- 第二个参数:部分 getters 对象
- 第三个参数:根 state
- 第四个参数:根 getters
(state, getters, rootState, rootGetters) => {...}
- 留神 getter 函数的参数是有程序的,而 action 函数是第一个参数对像没有程序
-
-
- 循环 moudle 中的 (_children) 对象,顺次执行 (installModule) 办法
-
<font color=red>store._modules.getNamespace(path)</font>
- 次要作用:拼接 module 的 path,而后赋值给 namespace
-
<font color=red>store._withCommit(fn)</font>
-
包装传入的 mutation 函数
- 在 mutation 函数执行时将 this._committing 设置 true
- 在 mutation 函数执行后将 this._committing 设置 false
- 这样能够保障批改 state 只能通过 mutation 函数,间接批改的话没有设置 this._committing 为 true,则证实不是通过 mutation 在批改
-
-
<font color=red>Vue.set(parentState, moduleName, module.state)</font>
- 将 moudles 中的 module 中的 state 合并到父 state 上
-
<font color=red>makeLocalContext(store, namespace, path)</font>
- 生成 local 对象,具备 dispatch, commit, getters, state 等属性
-
<font color=red>module.forEachMutation(fn)</font>
- 循环遍历以后 module 中的 mutations 对象,将 value 和 key 传入 fn(mutation, key)
- 拼接 namespace 和 key
- 调用 registerMutation(store, namespacedType, mutation, local)
-
<font color=red>registerMutation(store, namespacedType, mutation, local)</font>
-
向 (store._mutations[type] ) 数组中 push 一个 (mutation 包装函数)
-
store._mutations[type]
type
是namespace/getter 函数的函数名
,比方像这样cart/incrementItemQuantity
value
是wrappedMutationHandler (payload)
这样的函数
- 包装函数就是给 mutation 函数增加一层壳,传入 payload,再外面调用 mutation handle 函数
-
留神:
- store._mutations 是一个对象
- 每个 store._mutations[type] 是一个数组
-
-
-
<font color=red>module.forEachAction(fn)</font>
- 和
module.forEachMutation(fn)
相似 -
循环遍历以后 module 中的 actions 对象
- 用 (action.root) 来失去不同的 type
- 用 (action.handle) 是否存在来赋值 handle
- 调用
registerAction(store, type, handler, local)
函数
- 和
-
<font color=red>registerAction(store, type, handler, local)</font>
- 向 (store._actions[type] ) 数组中 push 一个 (action 包装函数 wrappedActionHandler)
-
<font color=red>wrappedActionHandler(payload)</font>
- 会在外部调用 (action 或者 action.handler) 函数命名为 handler 函数
- 如果 handler 函数的返回值不是 promise,就将返回值转换成 promise 对象,并 resolve,而后返回 fulfilled 状态的 promise 对象
-
<font color=red>handler()</font>
- 绑定 this 到 store
-
参数:
-
第一个参数是一个对象,具备以下属性
- dispatch
- commit
- getters
- state
- rootGetters
- rootState
- 第二个参数是 payload
-
-
<font color=red>module.forEachGetter(fn)</font>
- 循环遍历 module 中的 getters 中的所有 getter
- 将 value 和 key 传给 fn(value, key)
-
<font color=red>registerGetter(store, namespacedType, getter, local)</font>
-
给 (store._wrappedGetters) 增加属性办法
- key => namespace + key
- value => wrappedGetter
-
<font color=red>wrappedGetter</font>
- getter 的包装函数,
(store) => getter(localState, localGetters, rootState, rootGetters)
- 参数:部分 state getters,根 state getter
- getter 的包装函数,
-
<font color=DarkOrchid>commit (_type, _payload, _options) </font>
-
次要做了以下事件:
-
如果第一个参数是对象,就革新参数
- (一) store.commit(‘increment’, {amount: 10})
- (二) store.commit({type: ‘increment’,amount: 10})
- 将第二种革新成第一种
-
寻找该 mutaation,即在 this._mutations 中通过 key 寻找 mutation 对应的 [mutation]
- 没找到报错
- 找到循环数组中寄存的 mutation 包装函数,外面会调用真正的 mutation handler 函数
- 浅拷贝 this._subscribers 而后遍历该数组,调用外面的 subscribe 函数 => 即更改 state 后须要响应视图等
-
-
subscribe
-
subscribe(handler: Function): Function
store.subscribe((mutation, state) => {...})
- 订阅 store 的 mutation
- handler 会在每个 mutation 实现后调用,接管 mutation 和通过 mutation 后的状态作为参数
- 要进行订阅,调用此办法返回的函数即可进行订阅
-
<font color=DarkOrchid>dispatch (_type, _payload) </font>
-
次要做了以下事件
- 革新参数
-
执行 this._actionSubscribers 中的 before 钩子的 action 监听函数
- before 示意订阅处理函数的被调用机会应该在一个 action 散发之前调用
-
执行 action 函数
- this._actions[type] 数组长度大于 1,就 promise.all 包装,保障 result 是所有 promise 都 resolve
- this._actions[type] 数组长度小于等于 1,间接调用
-
当所有 promise 都 resolve 后,即所有异步都返回后果后,执行_actionSubscribers 中 after 钩子的 action 监听函数
- after 示意订阅处理函数的被调用机会应该在一个 action 散发之后调用
- 并将最终的后果 resolve 进来,返回 promise 对象
-
subscribeAction
-
subscribeAction(handler: Function): Function
store.subscribeAction((action, state) => {...})
- 订阅 store 的 action
- handler 会在每个 action 散发的时候调用并接管 action 形容和以后的 store 的 state 这两个参数
-
留神:
- 从 3.1.0 起,subscribeAction 也能够指定订阅处理函数的被调用机会应该在一个 action 散发之前还是之后 (默认行为是之前)
- 即能够指定
store.subscribeAction({before: (action, state) => {},after: (action, state) => {}})
-
源码代码
(1) this._modules = new ModuleCollection(options)
-
次要作用
- 赋值 this.root 为根模块
- 如果模块中存在 modules 属性,就给父模块的 _children 属性对象中增加该模块的对应关系
// 文件目录 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 环境,就断言传入的 options 中的各个属性对象的每个 key 对于应的 value 的类型,如果不是应该有的类型,就抛错
}
// dev 环境并且传入的 options 合乎断言要求 或者 prod 环境
// 则进入上面代码
const newModule = new Module(rawModule, runtime) // ---------------------------------------- 剖析 1
// 创立一个 module 实例
// module
// 实例属性 runtime, _children, _rawModule, state
// 原型属性 getChild,addChild,namespaced,update,forEachGetter ... 等
if (path.length === 0) {
this.root = newModule
// 数组长度是 0,就 new Module 实例赋值给 root 属性
} else {
// 问题:什么时候 path.length !== 0
// 答案:上面会判断是否有 modules 属性,当存在 modules 属性时,会再次调用 register,此时长度不为 0
const parent = this.get(path.slice(0, -1)) // -------------------------------------------- 剖析 2
// parent:获取父 module
parent.addChild(path[path.length - 1], newModule) // ------------------------------------- 剖析 3
// 给父 module._children 对象中增加 key 和 value,即父 modlue 的子 module
}
if (rawModule.modules) {
// 如果该 module 中存在 modules 对象,就遍历 modules
forEachValue(rawModule.modules, (rawChildModule, key) => { // --------------------------- 剖析 4
this.register(path.concat(key), rawChildModule, runtime)
// path.concat(key) => 相似 [module1],[module2],留神 concat 不会扭转 path
// rawChildModule => module1 module2
})
}
}
-
剖析 1
// 文件目录 src/module/module.js export default class Module {constructor (rawModule, runtime) { this.runtime = runtime // Store some children item this._children = Object.create(null) // _children 对象 // 用来寄存 modules 属性中的所有子 moudle // key => moudle 的名字 // value => module 对象,外面可能有 state,mutations,actions,getters 等 // Store the origin module object which passed by programmer this._rawModule = rawModule // 以后模块 const rawState = rawModule.state // Store the origin module's state this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} // state // 函数:就调用,返回 stated 对象 // 对象:间接赋值 } addChild (key, module) {this._children[key] = module } getChild (key) {return this._children[key] } }
-
剖析 2
get (path) {// 当 path 是空数组时,[].reducer((a,b) => a.getChild(key), this.root) 返回 root return path.reduce((module, key) => {return module.getChild(key) }, this.root) }
-
剖析 3
addChild (key, module) {this._children[key] = module // 给 module 实例的_children 属性对象中增加映射 // key => moudle 名 // value => module 对象 }
-
剖析 4
export function forEachValue (obj, fn) {Object.keys(obj).forEach(key => fn(obj[key], key)) // 遍历 obj // fn(value, key) // 将 obj 中的 key 作为第二个参数 // 将 obj 中的 value 作为第一个参数 }
(2) Store 类的 constructor
Store 类的构造函数 - 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)
// 如果 Vue 不存在 并且 window 存在 并且 window.Vue 存在,就主动装置
}
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.`)
// 断言
// Vue.use(Vuex) 肯定要在 new Vuex.store() 前调用
// 是否存在 promise,因为 vuex 依赖 promise
// Store 必须通过 new 命令调用
}
const {plugins = [], // 解构赋值 plugins 和 strict,并赋默认值
strict = false
} = options
// store internal state
this._committing = false
// _committing
// 标记位,默认是 false
// 批改 state 都会用 _withCommit 函数包装,在执行批改 state 函数中,this._committing=true, 批改后置为 false
// 用来判断是否通过 mutation 函数批改 state,只有通过 mutation 批改 state 才是非法的
// 比方在合并 state 的操作中会用到
this._actions = Object.create(null)
// _actions
// 寄存所有 moudle 的 action
// {// cart/addProductToCart: [ƒ], f 指的是 wrappedActionHandler (payload)
// cart/checkout: [ƒ],
// products/getAllProducts: [],
// }
this._actionSubscribers = []
// _actionSubscribers
// 收集 action 监听函数
// 留神辨别:// this._subscribers = [] mutation 监听函数
this._mutations = Object.create(null)
// _mutations
// 寄存所有 moudle 的 mutation
// {// cart/incrementItemQuantity: [ƒ], f 指的是 wrappedMutationHandler
// cart/pushProductToCart: [ƒ],
// cart/setCartItems: [ƒ],
// cart/setCheckoutStatus: [ƒ],
// products/decrementProductInventory: [ƒ],
// products/setProducts: [ƒ],
// }
this._wrappedGetters = Object.create(null)
// _wrappedGetters
// 寄存所有 module 中的 getter
// {// cart/cartProducts: ƒ wrappedGetter(store)
// cart/cartTotalPrice: ƒ wrappedGetter(store)
// }
// 留神:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!// 1. this._wrappedGetters 在 resetStoreVM(this, state) 会用到
// 2. 留神辨别 (store.getters) 和 (store._wrappedGetters)
this._modules = new ModuleCollection(options)
// _modules
// ModuleCollection 类次要就是收集所有的 moudle
// {
// root: {
// runtime: false,
// state: {}, // 留神此时还没有合并 state
// _children: { // 把 moudules 中的 module 放入父模块的 _children 属性中
// cart: Module,
// products: Module,
// },
// _rawModule: 根 module
// }
// }
this._modulesNamespaceMap = Object.create(null)
// _modulesNamespaceMap
// namespace 和 mdule 的一一映射对象
// {
// cart/: Module
// products/: Module
// }
this._subscribers = []
// _subscribers
// mutation 监听函数
// 留神辨别:// this._actionSubscribers = [] action 监听函数
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)
// 绑定 dispatch 函数的 this 到 store 实例上
}
this.commit = function boundCommit (type, payload, options) {return commit.call(store, type, payload, options)
// 绑定 commit 函数的 this 到 store 实例上
}
// strict mode
this.strict = strict
// 严格模式
// 默认是 flase
// 严格模式下,只能通过 mutation 批改 state
// 在生产环境中倡议敞开,因为严格模式会深度监测状态树来检测不合规的状态变更,有性能损耗
const state = this._modules.root.state
// 根 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) // ----------------------------------------------------------- 上面会剖析
// apply plugins
plugins.forEach(plugin => plugin(this))
// 循环遍历插件,传入 stroe
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
// 是否存在:传入 new Vuex.stroe(options) 中的 options 中存在 devtools
// 存在:options.devtools
// 不存在: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
// 当 path 数组长度是 0,则是根 module
const namespace = store._modules.getNamespace(path)
// 获取 namespace => module 名 + /
// => 或者 ''
// register in namespace map
if (module.namespaced) {
// module.namespaced
// 每个 module 能够有 namespaced 属性,是一个布尔值
// 示意开启部分 module 命名
// 命名空间官网介绍 (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('/')}`)
// 反复了
}
store._modulesNamespaceMap[namespace] = module
// _modulesNamespaceMap
// 建设 module 和 nameSpace 的映射关系
// key : namespace
// vlaue: module
// {
// cart/: Module
// products/: Module
// }
}
// set state
if (!isRoot && !hot) {
// 不是根模块 并且 hot 为 flase,才会进入判断
const parentState = getNestedState(rootState, path.slice(0, -1))
// parentState
// 获取该模块的
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)
// 合并所有 modules 中的 state 到 rootState
})
}
const local = module.context = makeLocalContext(store, namespace, path)
// 申明 module.context 并赋值
// local
// dispatch
// commit
// getters
// state
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
// 把所有 modules 中的每一个 mutations 中的的 mutation 函数增加到 _mutations 对象上
// _mutations 对象如下的格局
// {// cart/incrementItemQuantity: [ƒ], f 指的是 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)
// 把所有 module 中的每一个 actions 中的的 action 函数增加到 _actions 对象上
// _actions 对象如下的格局
// {// cart/addProductToCart: [ƒ], f 指的是 wrappedActionHandler (payload)
// cart/checkout: [ƒ]
// products/getAllProducts: []
// }
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
// 把所有 modul 中的每一个 getter 函数增加到 _wrappedGetters 对象上
// 寄存所有 module 中的 getter
// {// cart/cartProducts: ƒ wrappedGetter(store)
// cart/cartTotalPrice: ƒ wrappedGetter(store)
// }
})
module.forEachChild((child, key) => {installModule(store, rootState, path.concat(key), child, hot)
// 循环遍历 module._children 对象,并在每次循环中执行 installModule 办法
})
}
(4) resetStoreVM(this, this.state, hot)
function resetStoreVM (store, state, hot) {
// resetStoreVM
// 参数
// store
// state
// hot
const oldVm = store._vm
// oldVm 缓存旧的 store._vm
// bind store public getters
store.getters = {}
// 在 store 实例上增加 getters 属性对象,初始值是一个空对象
// 留神:// 1. 辨别 store._wrappedGetters 和 store.getters
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
// wrappedGetters
// 缓存 store._wrappedGetters
const computed = {}
// 申明 computed 变量
forEachValue(wrappedGetters, (fn, key) => {
// 循环 wrappedGetters,将 value 和 key 作为参数传入 forEachValue 的第二个参数函数
computed[key] = partial(fn, store)
// 1. partial 是这样一个函数
// function partial (fn, arg) {// return function () {// return fn(arg)
// }
// }
// 2. 即 computed[key] = () => fn(store)
// fn 就是具体的 getter 函数
Object.defineProperty(store.getters, key, {get: () => store._vm[key],
enumerable: true // 可枚举
})
// 1. 给 store.getters 对象增加 key 属性
// 2. 拜访 stroe.getters[key] = store._vm[key]
// 即拜访 store.getter.aaaa 相当于拜访 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
// 缓存 Vue.config.silent
Vue.config.silent = true
// 开启勾销正告
// 勾销 Vue 所有的日志与正告,即在 new Vue() 时不会有正告
store._vm = new Vue({
data: {?state: state // 11. 将传入的 state 赋值给 data 中的 ?state 属性},
computed // 22. 将 computed 变狼赋值给 Vue 中的 computed 属性 (computed[key] = () => fn(store))
})
// store._vm = new Vue(...)
// 通过下面 1122 使得 state 和 () => fn(store) 具备响应式
Vue.config.silent = silent
// 敞开勾销正告
// enable strict mode for new vm
if (store.strict) {enableStrictMode(store)
// 使能严格模式,保障批改 store 只能通过 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
// 解除援用
})
}
Vue.nextTick(() => oldVm.$destroy())
// dom 更新后,捣毁 vue 实例
// 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)
// 结构 commit 须要的参数类型
// 1. store.commit('increment', 10)
// 2. store.commit('increment', {amount: 10})
// 3. store.commit({type: 'increment',amount: 10})
// 就是将第 3 种状况结构成第 2 种的状况
const mutation = {type, payload}
const entry = this._mutations[type]
// entry 找到须要提交的 mutation 函数组成的数组,是一个数组,数组种就是包装过后的 mutation handle 函数
if (!entry) {
// 没找到该 mutation
if (__DEV__) {console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
// this._withCommit 保障批改 state 是非法伎俩,即 this._committing 在批改是是 true
entry.forEach(function commitIterator (handler) {handler(payload)
// 传入参数执行 mutation handle 函数
})
})
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))
// 浅拷贝 this._subscribers 而后遍历该数组,调用外面的 subscribe 函数
// 即更改 state 后须要响应视图等
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)
// 结构 commit 须要的参数类型
// 1. store.dispatch('increment')
// 2. store.dispatch('incrementAsync', {amount: 10})
// 3. store.dispatch({type: 'incrementAsync', amount: 10})
// 就是将第 3 种状况结构成第 2 种的状况
const action = {type, payload}
const entry = this._actions[type]
if (!entry) {
// 没找到该 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 钩子 action 监听函数
// before 示意订阅处理函数的被调用机会应该在一个 action 散发之前调用
} 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)
// 长度大于 1,promise.all() 保障 result 所有 resolve
// 长度小于等于 1,间接调用
return new Promise((resolve, reject) => {
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
// after 示意订阅处理函数的被调用机会应该在一个 action 散发之后调用
} catch (e) {if (__DEV__) {console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
// resolve 最终后果
}, 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
-
首先是 mapstate 如何应用
官网的例子 computed: { ...mapState('some/nested/module', { a: state => state.a, b: state => state.b }) } 当映射的计算属性的名称与 state 的子节点名称雷同时,咱们也能够给 mapState 传一个字符串数组 computed: mapState([ // 映射 this.count 为 store.state.count 'count' ])
-
源码
- 把 state 结构成 computed 对象返回
- 依据 namespace 把 (部分的 state 和 getter 作为参数) 传给传入的参数对象的 (属性函数)
export const mapState = normalizeNamespace((namespace, states) => { // normalizeNamespace // 返回改装参数后的,f(namespace, states) // 改成上面的参数模式 // ...mapState('some/nested/module', { // a: state => state.a, // b: state => state.b // }) const res = {} if (__DEV__ && !isValidMap(states)) { // 如果是 dev 环境 并且 states 不是是一个对象或者一个数组 // 报错 // 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. 如果 states 是数组,返回一个数组,每个成员是一个对象 ({ key: key, val: key}) // 2. 如果 states 是对象,返回一个数组,每个成员是一个对象 ({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 // 在 (store._modulesNamespaceMap) 对象中找到 (参数 namespace) 对应的 (module) if (!module) {return} state = module.context.state // 获取该 module 种的部分 state getters = module.context.getters // 获取该 module 种的部分 getters } return typeof val === 'function' ? val.call(this, state, getters) : state[val] // val 是一个函数,就调用函数 val.call(this, state, getters) 返回 // val 不是函数,就间接返回 state[val] } // mark vuex getter for devtools res[key].vuex = true }) return res // 最初返回 res 对象 // res 对象会作为组件的 computed })
------
function normalizeNamespace (fn) {return (namespace, map) => {if (typeof namespace !== 'string') {
// 如果 namespace 不是一个字符串
// 阐明传入只传入了一个参数
// 就把 namespace='',把第一个不是字符串的参数赋值给第二个参数
map = namespace
namespace = ''} else if (namespace.charAt(namespace.length - 1) !=='/') {
namespace += '/'
// 没有 / 则增加
}
return fn(namespace, map)
// 返回转换参数过后的 fn
}
}
------
function getModuleByNamespace (store, helper, namespace) {// 1. getModuleByNamespace(this.$store, 'mapState', namespace)
const module = store._modulesNamespaceMap[namespace]
// 找到 namespace 对应的 module
if (__DEV__ && !module) {console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
// 返回 module
}
------
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] }))
// 1. 如果是数组,返回一个数组,每个成员是一个对象 ({key: key, val: key})
// 2. 如果是对象,返回一个数组,每个成员是一个对象 ({key:key, val: map[key] })
}
应用中的留神点
mapState – (带 namespace 和不带 namespace)
mapGetters
mapMutations
mapActions
-
ui 组件中
<template> <div class="vuex"> <div> <div style="font-size: 30px">vuex</div> <div>dispatch 一个 action => store.dispatch({type: 'actionName', payload: ''})</div> <div>commit 一个 mutation => store.dispatch({type: 'actionName', payload: ''})</div> <div>------</div> <button @click="changeCount"> 点击批改 vuexModule 中的 count+1 </button> <div>{{moduleState.count}}</div> <div>------</div> <div><button @click="getName"> 点击,发动申请,获取 name 数据,利用 Vuex actions - 不传参数 </button></div> <div><button @click="getName2"> 点击,发动申请,获取 name 数据,利用 Vuex actions - 传参 </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 => { // --------------- 命名为 rootState return state; // ----------------------- 这里的 state 是 rootMoudule 的 state } }), ...mapState("vuexModule", { // ------------ namespace moduleState: state => { // -------------- 命名为 moduleState return state; // ---------------------- 这里的 state 是 moudles 中 vuexModule 的 state } }), ...mapGetters("vuexModule", { // ---------- 第二个参数是对象,即能够批改 getter 的名字 changeGetterName: "square" }), ...mapGetters("vuexModule", ['square']), // 第二个参数是数组 }, methods: { ...mapMutations('vuexModule', {addCountChangeName: 'AddCount' // ------- 对象形式,能够批改 mutation 的名字}), ...mapActions('vuexModule', ['getData', 'getData2']), // mapActions changeCount() {this.addCountChangeName(1) // ----------- 参数将作为 mutation(state, payload) 的 payload }, getName() {this.getData() // ----------------------- 不传参给 action 函数,解决异步 }, getName2() { this.getData2({ // ----------------------- 传参给 action 函数,解决异步 url: "/home", method: "get" }) } }, mounted() { console.log( this.rootState.vuexModule.count, "没有 namespace 的 mapState 获取的 state - 拜访 coount" ); console.log( this.moduleState.count, "具备 namespace 的 mapState 获取的 state - 拜访 count" ); console.log(this.changeGetterName, 'mapGetters 第二个参数是对象'); console.log(this.square, 'mapGetters 第二个参数是数组'); } }; </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 不穿参给 action 函数 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 穿参给 action 函数 // action(context, payload) // 第一个参数:context 和 store 具备雷同的 api // 第二个参数:payload 是 dispatch 一个 action 穿过来的参数 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
材料
vuex 官网文档 https://vuex.vuejs.org/zh/
川神 较全面 https://juejin.im/post/684490…
2.3.1 版本 Vuex, 过程具体:https://juejin.im/post/684490…
yck https://juejin.im/post/684490…