>PS:個人筆記,僅供參考,需要深入了解請閱讀參考資料。
[TOC]
# 參考資料
[https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html#sfc](https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html#sfc)
# Vuex 初始化
## 安裝
和 Vue-Router 一樣,Vuex 也同樣存在一個靜態的`install`方法,它的定義在`src/store.js`中:
```js
export function install (_Vue) {
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)
}
```
`install`的邏輯很簡單,把傳入的`_Vue`賦值給`Vue`并執行了`applyMixin(Vue)`方法,它的定義在`src/mixin.js`中:
```js
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
const options = this.$options
// store injection
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
}
}
}
```
`applyMixin`全局混入了一個`beforeCreate`鉤子函數,它的實現非常簡單,就是把`options.store`保存在所有組件的`this.$store`中,這個`options.store`就是`Store`對象的實例。
## Store 實例化
我們在`import Vuex`之后,會實例化其中的`Store`對象,返回`store`實例并傳入`new Vue`的`options`中,也就是我們剛才提到的`options.store`.就像下面這樣:
```js
export default new Vuex.Store({
actions,
getters,
state,
mutations,
modules
// ...
})
```
`Store`對象的構造函數接收一個對象參數,它包含`actions`、`getters`、`state`、`mutations`、`modules`等 Vuex 的核心概念,它的定義在`src/store.js`中:
```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 (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.`)
}
const {
plugins = [],
strict = false
} = options
// store internal state
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()
// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
// strict mode
this.strict = strict
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
if (Vue.config.devtools) {
devtoolPlugin(this)
}
}
}
```
這里有三個關鍵點:
- 初始化模塊
```js
this._modules = new ModuleCollection(options)
```
- 安裝模塊
```js
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
```
- 初始化`store_.vm`
```js
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)
```
### 初始化模塊
在分析模塊初始化之前,我們先來了解一下模塊對于 Vuex 的意義:由于使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象,當應用變得非常復雜時,`store`對象就有可能變得相當臃腫。為了解決以上問題,Vuex 允許我們將`store`分割成模塊(module)。每個模塊擁有自己的`state`、`mutation`、`action`、`getter`,甚至是嵌套子模塊——從上至下進行同樣方式的分割:
```js
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... },
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態
```
`Module`是用來描述單個模塊的類,它的定義在`src/module/module.js`中:
```js
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule
const rawState = rawModule.state
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
get namespaced () {
return !!this._rawModule.namespaced
}
addChild (key, module) {
this._children[key] = module
}
removeChild (key) {
delete this._children[key]
}
getChild (key) {
return this._children[key]
}
update (rawModule) {
this._rawModule.namespaced = rawModule.namespaced
if (rawModule.actions) {
this._rawModule.actions = rawModule.actions
}
if (rawModule.mutations) {
this._rawModule.mutations = rawModule.mutations
}
if (rawModule.getters) {
this._rawModule.getters = rawModule.getters
}
}
forEachChild (fn) {
forEachValue(this._children, fn)
}
forEachGetter (fn) {
if (this._rawModule.getters) {
forEachValue(this._rawModule.getters, fn)
}
}
forEachAction (fn) {
if (this._rawModule.actions) {
forEachValue(this._rawModule.actions, fn)
}
}
forEachMutation (fn) {
if (this._rawModule.mutations) {
forEachValue(this._rawModule.mutations, fn)
}
}
}
```
對于每個模塊而言,`this._rawModule`表示模塊的配置,`this._children`表示它的所有子模塊,`this.state`表示這個模塊定義的`state`。
### 安裝模塊
初始化模塊后,執行安裝模塊的相關邏輯,它的目標就是對模塊中的`state`、`getters`、`mutations`、`actions`做初始化工作
```js
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}
const local = module.context = makeLocalContext(store, namespace, path)
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)
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
```
這里涉及到了命名空間的概念,默認情況下,模塊內部的`action`、`mutation`和`getter`是注冊在全局命名空間的——這樣使得多個模塊能夠對同一`mutation`或`action`作出響應。如果我們希望模塊具有更高的封裝度和復用性,可以通過添加`namespaced: true`的方式使其成為帶命名空間的模塊。當模塊被注冊后,它的所有`getter`、`action`及`mutation`都會自動根據模塊注冊的路徑調整命名。
### 初始化`store._vm`
`Store`實例化的最后一步,就是執行初始化`store._vm`的邏輯,它的入口代碼是:
```js
resetStoreVM(this, state)
```
```js
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
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())
}
}
```
`resetStoreVM`的作用實際上是想建立`getters`和`state`的聯系,因為從設計上`getters`的獲取就依賴了`state`,并且希望它的依賴能被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。因此這里利用了 Vue 中用`computed`計算屬性來實現。
`resetStoreVM`首先遍歷了`_wrappedGetters`獲得每個`getter`的函數`fn`和`key`,然后定義了`computed[key] = () => fn(store)`。我們之前提到過`_wrappedGetters`的初始化過程,這里`fn(store)`相當于執行如下方法:
```js
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
```
返回的就是`rawGetter`的執行函數,`rawGetter`就是用戶定義的`getter`函數,它的前 2 個參數是`local state`和`local getters`,后 2 個參數是`root state`和`root getters`。
接著實例化一個 Vue 實例`store._vm`,并把`computed`傳入:
```js
store._vm = new Vue({
data: {
$$state: state
},
computed
})
```
我們發現`data`選項里定義了`$$state`屬性,而我們訪問`store.state`的時候,實際上會訪問`Store`類上定義的`state`的`get`方法:
```js
get state () {
return this._vm._data.$$state
}
```
它實際上就訪問了`store._vm._data.$$state`。那么`getters`和`state`是如何建立依賴邏輯的呢,我們再看這段代碼邏輯:
```js
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
```
### 總結
我們要把`store`想象成一個數據倉庫,為了更方便的管理倉庫,我們把一個大的`store`拆成一些`modules`,整個`modules`是一個樹型結構。每個`module`又分別定義了`state`,`getters`,`mutations`、`actions`,我們也通過遞歸遍歷模塊的方式都完成了它們的初始化。為了`module`具有更高的封裝度和復用性,還定義了`namespace`的概念。最后我們還定義了一個內部的`Vue`實例,用來建立`state`到`getters`的聯系,并且可以在嚴格模式下監測`state`的變化是不是來自外部,確保改變`state`的唯一途徑就是顯式地提交`mutation`。
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs