Понять принцип реализации vuex

Vuex

Эта глава посвящена объяснению принципа реализации vuex.

Так как в коде много комментариев, код кажется избыточным, поэтому лучше скачать исходники, можно удалить комментарии и посмотреть, на самом деле кода не так много

Закуска

Что такое Векс?

Vuex — это шаблон управления состоянием, разработанный для приложений Vue.js. он принимаетЦентрализованное управление хранилищемсостояние всех компонентов приложения,

Это приложение управления состоянием состоит из следующих частей:

  • состояние, источник данных, управляющий приложением;
  • представление, которое декларативно сопоставляет состояние представлениям;
  • действия, которые представляют собой изменения состояния, вызванные пользовательским вводом в представлении.

Дается простая иллюстрация официальной концепции «одностороннего потока данных»:

Ядром каждого приложения Vuex является магазин. «Хранилище» — это, по сути, контейнер, который содержит большую часть состояния вашего приложения.

Vuex отличается от чистого глобального объекта следующими двумя способами:

  • Хранилище состояния Vuex является реактивным. Когда компонент Vue считывает состояние из хранилища, если состояние в хранилище изменяется, соответствующий компонент будет эффективно обновлен соответствующим образом. (также известный как МВВМ)
  • Вы не можете напрямую изменить состояние в хранилище. Единственный способ изменить состояние в магазинезафиксировать явноmutations.

Посмотрите на картинку, чтобы понять, как это работает:

Если вы понимаете эту картинку, вы можете знать, как работает vuex.

Обратите внимание:

  • Единственный способ изменить состояние — зафиксировать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
        }
    },
})

На этот раз, когда вы нажимаете кнопку «Добавить», вы можете добавить 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


^_<