本章重點講解一下 vuex 的實現原理,vue
由於代碼中註釋比較多,顯得代碼比較冗餘,因此最好把源碼下載下來,能夠把註釋刪了看一下,其實沒有多少代碼ios
Vuex是什麼?git
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,vue-router
這個狀態管理應用包含如下幾個部分:vuex
給出一張官方的「單向數據流」理念的簡單示意:後端
每個 Vuex 應用的核心就是 store(倉庫)。「store」基本上就是一個容器,它包含着你的應用中大部分的狀態 (state)。api
Vuex 和單純的全局對象有如下兩點不一樣:數組
mutations
。看圖瞭解工做原理:bash
若是理解了這張圖,你就能知道vuex的工做原理了app
須要注意的點:
mutations
actions
,其本質仍是提交mutations
actions
呢?能夠用組件Vue Components
使用dispatch
或者後端接口去觸發mutations
後,能夠動態的渲染組件Vue Components
以爲是否是少了什麼,沒錯,就是getters
下面原理實現的時候會說
首先把不須要的文件和代碼全刪了,經典化結構,以下:
App.vue
代碼:
<template>
<div>
<!-- vuex 把狀態放到一個公共的地方,哪一個組件使用,就直接能夠從公共的地方獲取狀態 -->
</div>
</template>
<script>
export default {
name:'app',
}
</script>
複製代碼
main.js
代碼:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from 'vue-router'
Vue.config.productionTip = false
new Vue({
name:'main',
router, //封裝了 router-view router-link $router $route
store, //寫到這裏,說明所有的組件均可以使用store
render: h => h(App)
}).$mount('#app')
複製代碼
store.js
代碼:
import Vue from 'vue'
//把裏面的全刪了,本身寫
// 引入本身的寫的vuex,裏面有一個對象{install},當你use時,會自動調用這個方法
//導入vuex {install Store}
import Vuex from './vuex'
Vue.use(Vuex)
//須要建立一個倉庫並導出
//當new的時候,給Vuex.js中傳入了一堆的東西
export default new Vuex.Store({
state:{
name:'Fan'
},
//getters中雖然是一個方法,可是用時,能夠把他看成屬性
getters:{ // 說白了,就是vue中data中的computed
},
// 改變狀態:異步請求數據 事件
mutations:{
},
actions:{
}
})
複製代碼
vuex.js
文件中的代碼先不寫,下面開始寫
上面準備工做作好,接下來就實現咱們的state
在vuex.js
中寫以下代碼(具體說明和操做已在代碼中註釋):
//定義一個Vue,讓全局均可以使用這個Vue
let Vue;
class Store{
//當new的時候,給Vuex.js中傳入了一堆的東西,在這裏接收須要用constructor
constructor(options){
// console.log(options); //打印出{state: {…}, getters: {…}, mutations: {…}, actions: {…}},就能夠拿到裏面的數據了
/*-------------------------------state原理-------------------------------------------------------------*/
//給每一個組件的$store上掛一個state,讓每一個組件均可以用 this.$store.state
this.state = options.state
//在state上面傳入一個name:'Fan'打印一下
// console.log(this.state); //打印結果 {name: "Fan"}
/*-------------------------------------------------------------------------------------------------*/
}
}
//install本質上就是一個函數
const install = (_Vue)=>{
// console.log('......'); //測試能不能調到這個方法,經測試能夠調到
//把構造器賦給全局Vue
Vue = _Vue;
//混入
Vue.mixin({
beforeCreate() { //表示在組件建立以前自動調用,每一個組件都有這個鉤子
// console.log(this.$options.name) //this表示每一個組件,測試,能夠打印出mian.js和App.vue中的name main和app
//保證每個組件都能獲得倉庫
//判斷若是是main.js的話,就把$store掛到上面
if(this.$options && this.$options.store){
this.$store = this.$options.store
}else{
//若是不是根組件的話,也把$store掛到上面,由於是樹狀組件,因此用這種方式
this.$store = this.$parent && this.$parent.$store
//在App.vue上的mounted({console.log(this.$store)})鉤子中測試,能夠獲得store ---> Store {}
}
},
})
}
//導出
export default {
install,
Store
}
複製代碼
這樣的話,所有的組件均可以使用this.$store.state
這個方法了
首先在store.js
中的getters
中定義兩個方法,用來測試:
//getters中雖然是一個方法,可是用時,能夠把他看成屬性
getters:{ // 說白了,就是vue中data中的computed
myName(state){
return state.name+'Jun'
},
myAge(){
}
},
複製代碼
而後在vuex.js
文件中的Store
類的constructor
中來寫咱們的代碼,以下:
class Store{
//當new的時候,給Vuex.js中傳入了一堆的東西,在這裏接收須要用constructor
constructor(options){
// console.log(options); //打印出{state: {…}, getters: {…}, mutations: {…}, actions: {…}},就能夠拿到裏面的數據了
/*------------------------------------state原理--------------------------------------------------------*/
//給每一個組件的$store上掛一個state,讓每一個組件均可以用 this.$store.state
// this.state = options.state
/*-------------------------------------------------------------------------------------------------*/
/* --------------------------------狀態響應式原理---------------------------------------------------------------- */
// 上面那種寫法不完美,當改變數據的時候,不能動態的渲染,因此須要把data中的數據作成響應式的
//_s在下面的 get state方法中使用
this._s = new Vue({
data:{
// 只有data中的數據纔是響應式
state:options.state
}
})
//在state上面傳入一個name:'Fan'打印一下
// console.log(this.state); //打印結果 {name: "Fan"}
/* ------------------------------------------------------------------------------------------------ */
/*---------------------------------getters原理-----------------------------------------------------------*/
//獲得倉庫中的getters,若是人家不寫getters的話,就默認爲空
let getters = options.getters || {}
// console.log(getters); //打印出一個對象,對象中是一個方法 {myName: ƒ}
//給倉庫上面掛載一個getters,這個getters和上面的那一個getters不同,一個是獲得,一個是掛載
this.getters = {}
//很差理解,由於人家會給你傳多個方法,因此使用這個api處理獲得的getters,獲得一個數組
//把store.js中的getters中再寫一個方法myAge,用來測試
// console.log(Object.keys(getters)); //打印出 ["myName", "myAge"]
//遍歷這個數組,獲得每個方法名
Object.keys(getters).forEach((getter)=>{
// console.log(getter); //打印出 myName myAge
Object.defineProperty(this.getters,getter,{
//當你要獲取getter的時候,會自動調用get這個方法
//必定要用箭頭函數,要否則this指向會出現問題
get:()=>{
console.log(this);
return getters[getter](this.state)
}
})
})
/*-------------------------------------------------------------------------------------------------*/
}
get state(){
return this._s.state
}
}
複製代碼
而後在App.vue
中測試:
<template>
<div>
<!-- vuex 把狀態放到一個公共的地方,哪一個組件使用,就直接能夠從公共的地方獲取狀態 -->
{{this.$store.state.name}}
<!-- 打印出 Fan -->
{{this.$store.getters.myName}}
<!-- 打印出 FanJun -->
</div>
</template>
<script>
export default {
name:'app',
mounted(){
console.log(this.$store);
}
}
</script>
複製代碼
先用人家的試一下:
在App.vue
中定義一個add
方法,上面定義一個按鈕用來觸發這個方法,代碼:
<template>
<div>
<!-- vuex 把狀態放到一個公共的地方,哪一個組件使用,就直接能夠從公共的地方獲取狀態 -->
{{this.$store.state.name}}
<!-- 打印出 Fan -->
{{this.$store.getters.myName}}
<!-- 打印出 FanJun -->
<hr>
{{this.$store.state.age}}
<button @click="add()">Add</button>
</div>
</template>
<script>
export default {
name:'app',
mounted(){
console.log(this.$store);
},
methods:{
add(){
//commit一個mutations
this.$store.commit('add',10)
}
}
}
</script>
複製代碼
在store.js
中用人家的vuex:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state:{
name:'Fan',
age:10
},
//getters中雖然是一個方法,可是用時,能夠把他看成屬性
getters:{ // 說白了,就是vue中data中的computed
myName(state){
return state.name+'Jun'
},
myAge(){
}
},
// 改變狀態:異步請求數據 事件
mutations:{
add(state,payload){
state.age += payload
}
},
})
複製代碼
此次當點擊Add按鈕的時候,就能實現 加10 操做
而後本身寫:
在store.js
中寫上mutations
,而且定義兩個方法:
// 改變狀態:異步請求數據 事件
mutations:{
add(state,payload){
state.age += payload
},
sub(){
}
},
複製代碼
而後在vuex.js
中的類Store
中實現:
class Store{
//當new的時候,給Vuex.js中傳入了一堆的東西,在這裏接收須要用constructor
constructor(options){
// console.log(options); //打印出{state: {…}, getters: {…}, mutations: {…}, actions: {…}},就能夠拿到裏面的數據了
/*-------------------------------state原理-------------------------------------------------------------*/
//給每一個組件的$store上掛一個state,讓每一個組件均可以用 this.$store.state
// this.state = options.state
/*----------------------------------------------------------------------------------------------------*/
/* --------------------------------狀態響應式原理---------------------------------------------------------------- */
// 上面那種寫法不完美,當改變數據的時候,不能動態的渲染,因此須要把data中的數據作成響應式的
//_s在下面的 get state() 方法中使用
this._s = new Vue({
data:{
// 只有data中的數據纔是響應式
state:options.state
}
})
//在state上面傳入一個name:'Fan'打印一下
// console.log(this.state); //打印結果 {name: "Fan"}
/* ----------------------------------------------------------------------------------------------------------------- */
/* ----------------------------------getters原理------------------------------------------------------------- */
//獲得倉庫中的getters,若是人家不寫getters的話,就默認爲空
let getters = options.getters || {}
// console.log(getters); //打印出一個對象,對象中是一個方法 {myName: ƒ}
//給倉庫上面掛載一個getters,這個getters和上面的那一個getters不同,一個是獲得,一個是掛載
this.getters = {}
//很差理解,由於人家會給你傳多個方法,因此使用這個api處理獲得的getters,獲得一個數組
//把store.js中的getters中再寫一個方法myAge,用來測試
// console.log(Object.keys(getters)); //打印出 ["myName", "myAge"]
//遍歷這個數組,獲得每個方法名
Object.keys(getters).forEach((getter)=>{
// console.log(getter); //打印出 myName myAge
Object.defineProperty(this.getters,getter,{
//當你要獲取getter的時候,會自動調用get這個方法
//必定要用箭頭函數,要否則this指向會出現問題
get:()=>{
// console.log(this);
return getters[getter](this.state)
}
})
})
/* -------------------------------------------------------------------------------------------------- */
/* ---------------------------------------mutatios原理----------------------------------------------------------- */
//和getters思路差很少
//獲得mutations
let mutations = options.mutations || {}
// console.log(mutations); //{add: ƒ}
//掛載mutations
this.mutations = {}
//拿到對象中的一堆方法
Object.keys(mutations).forEach((mutation)=>{
// console.log(mutation); //add sub
this.mutations[mutation] = (payload)=>{
mutations[mutation](this.state,payload)
}
})
//打印看一下,正確
// console.log(mutations); //{add: ƒ, sub: ƒ}
//可是他比較噁心,須要實現commit,在下面實現
/* -------------------------------------------------------------------------------------------------- */
}
//給store上掛一個commit,接收兩個參數,一個是類型,一個是數據
commit(type,payload){
//{add: ƒ, sub: ƒ}
//把方法名和參數傳給mutations
this.mutations[type](payload)
}
get state(){
return this._s.state
}
}
複製代碼
在App.vue
中測試:
<template>
<div>
<!-- vuex 把狀態放到一個公共的地方,哪一個組件使用,就直接能夠從公共的地方獲取狀態 -->
{{this.$store.state.name}}
<!-- 打印出 Fan -->
{{this.$store.getters.myName}}
<!-- 打印出 FanJun -->
<hr>
{{this.$store.state.age}}
<button @click="add()">Add</button>
</div>
</template>
<script>
export default {
name:'app',
mounted(){
// console.log(this.$store);
},
methods:{
add(){
//commit一個mutations
this.$store.commit('add',10)
}
}
}
</script>
複製代碼
由於代碼比較冗餘,因此我簡化了代碼,就是把公共的方法Object.keys(obj).forEach(key => { callback(key, obj[key]) })
抽離出來。
能夠下載源碼看一下,這裏就很少說了
一樣的,在vuex.js
中的類Store
中實現,由於我簡化了代碼,因此總體複製下來看一下,
這裏把dispatch
和commit
方法換成了箭頭函數,防止this
指向出現問題
//定義一個Vue,讓全局均可以使用這個Vue
let Vue;
// forEach是用來循環一個對象
const forEach = (obj, callback) => {
// 把數組中的每個key獲得 objc[key]
// key value ----> callback
Object.keys(obj).forEach(key => {
callback(key, obj[key])
})
}
class Store {
//當new的時候,給Vuex.js中傳入了一堆的東西,在這裏接收須要用constructor
constructor(options) {
// console.log(options); //打印出{state: {…}, getters: {…}, mutations: {…}, actions: {…}},就能夠拿到裏面的數據了
/*-------------------------------state原理-------------------------------------------------------------*/
//給每一個組件的$store上掛一個state,讓每一個組件均可以用 this.$store.state
// this.state = options.state
/*----------------------------------------------------------------------------------------------------*/
/* ---------------------------------------狀態響應式原理--------------------------------------------------------- */
// 上面那種寫法不完美,當改變數據的時候,不能動態的渲染,因此須要把data中的數據作成響應式的
//_s在下面的 get state方法中使用
this._s = new Vue({
data: {
// 只有data中的數據纔是響應式
state: options.state
}
})
/* ----------------------------------------------------------------------------------------------------------------- */
/* ----------------------------------------getters原理------------------------------------------------------- */
//在state上面傳入一個name:'Fan'打印一下
// console.log(this.state); //打印結果 {name: "Fan"}
//獲得倉庫中的getters,若是人家不寫getters的話,就默認爲空
let getters = options.getters || {}
// console.log(getters); //打印出一個對象,對象中是一個方法 {myName: ƒ}
//給倉庫上面掛載一個getters,這個getters和上面的那一個getters不同,一個是獲得,一個是掛載
this.getters = {}
//很差理解,由於人家會給你傳多個方法,因此使用這個api處理獲得的getters,獲得一個數組
//把store.js中的getters中再寫一個方法myAge,用來測試
// console.log(Object.keys(getters)); //打印出 ["myName", "myAge"]
forEach(getters, (getterName, value) => {
Object.defineProperty(this.getters, getterName, {
get: () => {
return value(this.state)
}
})
})
/* -------------------------------------------------------------------------------------------------- */
/* ----------------------------------------mutatios原理---------------------------------------------------------- */
//和getters思路差很少
//獲得mutations
let mutations = options.mutations || {}
// console.log(mutations); //{add: ƒ}
//掛載mutations
this.mutations = {}
forEach(mutations, (mutationName, value) => {
this.mutations[mutationName] = (payload) => {
value(this.state, payload)
}
})
//打印看一下,正確
// console.log(mutations); //{add: ƒ, sub: ƒ}
//可是他須要實現commit,在下面實現
/* -------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------actions原理----------------------------------------------------- */
//和上面兩種大同小異,很少註釋了
let actions = options.actions || {}
this.actions = {};
forEach(actions, (action, value) => {
this.actions[action] = (payload) => {
value(this, payload)
}
})
/* -------------------------------------------------------------------------------------------------- */
}
// type是actions的類型
dispatch = (type, payload) => {
this.actions[type](payload)
}
//給store上掛一個commit,接收兩個參數,一個是類型,一個是數據
commit = (type, payload) => {
//{add: ƒ, sub: ƒ}
this.mutations[type](payload)
}
get state() {
return this._s.state
}
}
//install本質上就是一個函數
const install = (_Vue) => {
// console.log('......'); //測試能不能調到這個方法,經測試能夠調到
//把構造器賦給全局Vue
Vue = _Vue;
//混入
Vue.mixin({
beforeCreate() { //表示在組件建立以前自動調用,每一個組件都有這個鉤子
// console.log(this.$options.name) //this表示每一個組件,測試,能夠打印出mian.js和App.vue中的name main和app
//保證每個組件都能獲得倉庫
//判斷若是是main.js的話,就把$store掛到上面
if (this.$options && this.$options.store) {
this.$store = this.$options.store
} else {
//若是不是根組件的話,也把$store掛到上面,由於是樹狀組件,因此用這種方式
this.$store = this.$parent && this.$parent.$store
//在App.vue上的mounted()鉤子中測試,能夠獲得store ---> Store {}
}
},
})
}
//導出
export default {
install,
Store
}
複製代碼
在mutations
中添加一個異步方法:
mutations: {
add(state, payload) {
state.age += payload
},
sub() {
},
asyncSub(state, payload) {
state.age -= payload
}
},
複製代碼
在store.js
中寫一個actions
actions: {
asyncSub({commit}, payload) {
setTimeout(() => {
commit("asyncSub", payload)
}, 2000)
}
}
複製代碼
最後在App.vue
中定義方法測試:
<template>
<div>
<!-- vuex 把狀態放到一個公共的地方,哪一個組件使用,就直接能夠從公共的地方獲取狀態 -->
{{this.$store.state.name}}
<!-- 打印出 Fan -->
{{this.$store.getters.myName}}
<!-- 打印出 FanJun -->
<hr> {{this.$store.state.age}}
<!-- 同步加 -->
<button @click="add">Add</button>
<!-- 異步減 -->
<button @click="sub">Async Sub</button>
</div>
</template>
<script>
export default {
name: "app",
mounted() {
// console.log(this.$store);
// 是異步的
setTimeout(() => {
this.$store.state.age = 666;
}, 1000);
// 是同步的
console.log(this.$store.state);
},
methods: {
add() {
//commit一個mutations
this.$store.commit("add", 10);
},
sub(){
this.$store.dispatch("asyncSub",10)
}
}
};
</script>
複製代碼
vuex.js
代碼其實並無多少代碼
let Vue;
const forEach = (obj, callback) => {
Object.keys(obj).forEach(key => {
callback(key, obj[key])
})
}
class Store {
constructor(options) {
this._s = new Vue({
data: {
state: options.state
}
})
let getters = options.getters || {}
this.getters = {};
forEach(getters, (getterName, value) => {
Object.defineProperty(this.getters, getterName, {
get: () => {
return value(this.state)
}
})
})
let mutations = options.mutations || {}
this.mutations = {};
forEach(mutations, (mutationName, value) => {
this.mutations[mutationName] = (payload) => {
value(this.state, payload)
}
})
let actions = options.actions || {}
this.actions = {};
forEach(actions,(actionName,value)=>{
this.actions[actionName] = (payload)=>{
value(this,payload)
}
})
}
dispatch=(type,payload)=>{
this.actions[type](payload)
}
commit=(type, payload)=>{
this.mutations[type](payload)
}
get state() {
return this._s.state
}
}
const install = _Vue => {
Vue = _Vue
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.store) {
this.$store = this.$options.store
} else {
this.$store = this.$parent && this.$parent.$store
}
}
})
}
export default { install, Store }
複製代碼
由於註釋太多,顯得很複雜,因此最好把源碼下載下來,本身去嘗試寫一下
附上源碼地址:Vuex實現原理