本章重点讲解一下 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实现原理