文章已同步至掘金:https://juejin.cn/post/6844903949938475022
欢迎访问😃,有任何问题都可留言评论哦~
Vuex是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,
这个状态管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
给出一张官方的“单向数据流”理念的简单示意:
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。
Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。(也就是所谓的MVVM)
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit)
mutations
。
看图了解工作原理:
如果理解了这张图,你就能知道vuex的工作原理了
需要注意的点:
- 改变状态的唯一途径就是提交
mutations
- 如果是异步的,就派发(dispatch)
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
上面准备工作做好,接下来就实现我们的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
这个方法了
实现getters
首先在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>
实现mutations
先用人家的试一下:
在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]) })
抽离出来。
可以下载源码看一下,这里就不多说了
实现actions
同样的,在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实现原理
评论区