• 技术文章 >web前端 >Vue.js

    深入了解vuex的实现原理

    青灯夜游青灯夜游2023-03-20 18:23:57转载313

    当面试被问vuex的实现原理,你要怎么回答?下面本篇文章就来带大家深入了解一下vuex的实现原理,希望对大家有所帮助!

    关于vuex就不再赘述,简单回顾一下:当应用碰到多个组件共享状态时,简单的单向数据流很容易被破坏:第一,多个视图依赖于同一状态;第二,来自不同视图的行为需要变更同一状态。若解决前者使用传参的方式,则不适用于多层嵌套的组件以及兄弟组件;若解决后者使用父子组件直接引用或事件变更和同步状态的多份拷贝,则不利于代码维护。

    所以,最好的办法是:把组件的共享状态抽取出,以一个全局单例模式管理!这也正是vuex背后的基本思想。

    image.png

    【相关推荐:vuejs视频教程web前端开发

    所以,vuex的大致框架如下:

    class Store {
        constructor() {
            // state
            // getters  
            // mutations
            // actions
        }
        // commit
        // dipatch
    }

    接下来,就写写看。

    一、创建vue项目

    vue create vue2-vuex//创建vue2项目
    
    yarn add vuex@next --save//安装vuex
    
    yarn serve//启动项目

    二、实现原理

    1、State

    (1)使用

    //store.js
    // 仓库
    import Vue from 'vue'
    import Vuex from 'vuex'
    import extra from './extra.js'
    
    Vue.use(Vuex) //引入vuex的方式,说明Store需要install方法
    export default new Vuex.Store({
        // 仓库数据源
        state: {
            count: 1,
            dowhat: 'addCount'
        },
    }
    //app.vue
    <template>
        <div class="testState">
            <p>{{mycount}}</p>
            <p>{{dowhat}}:{{count}}</p>
        </div>
    </template>
    
    <script>
    export default {
        import {
            mapState
        } from 'vuex'
        // 推荐方式
        computed: mapState()({
            mycount: state => state.count
        }),
        // 推荐方式的简写方式
        computed: {
            // 解构的是getters
            ...mapState(['count', 'dowhat'])
        },
      }
    </script>

    (2)注意

    由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性 中返回某个状态,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态

    Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)

    (3)实现

    所以除了Store内部的五大属性以外,还需要考虑插件的一个install方法,所以大致框架如下:

    class Store {
        constructor() {
            // state
            // getters  
            // mutations
            // actions
            //modules
        }
        // commit
        // dipatch
    }
    let Vuex = {
        Store,
        Install
    }
    export default Vuex

    所以,接下来就可以具体实现了,

    class Store {
        constructor(options) {
            // state
            this.state = options.state
        }
    }
    let install = function(_Vue) {
        _Vue.mixin({
            beforeCreate() { //在组件创建之前自动调用,每个组件都有这个钩子
                if (this.$options && this.$options.store) { //this.$options读取根组件
                    this.$store = this.$options.store
                } else {
                    this.$store = this.$parent && this.$parent.$store
                }
            }
        })
    }

    然而,上述的state的实现有一个缺点:当改变数据的时候,state的数据不能动态的渲染。所以如何把state里的数据成为响应式成为关键问题?实际上,类似vue里的data,也可以通过这种方式让其成为响应式。那么就得从install方法中传入Vue,所以改变后:

    let Vue=null
    class Store {
        constructor(options) {
            // state
            this.vm = new _Vue({
              data: {
                state: options.state//data中的数据才是响应式
              }
            })
        }
          get state() {
            return this.vm.state
        }
    }
    let install = function(_Vue) {//用于Vue.use(plugin)
        Vue=_Vue
        _Vue.mixin({
            onBeforeCreate() { //在组件创建之前自动调用,每个组件都有这个钩子
                if (this.$options && this.$options.store) { //this.$options读取根组件
                    this.$store = this.$options.store
                } else {
                    this.$store = this.$parent && this.$parent.$store
                }
            }
        })
    }

    2、getters

    (1)使用

    //store.js
    export default new Vuex.Store({
        // 计算属性
        getters: {
            // 这里的函数不需要调用,可以直接使用,官方默认前面有get
            getCount(state) {//接受 state 作为其第一个参数
                return state.count * 100;
            }
        },
    }

    (2)注意

    有时候我们需要从 store 中的 state 中派生出一些状态(比如增加,删除,过滤等等),Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算,Getter 接受 state 作为其第一个参数,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果

    (3)实现

     // getters
        let getters = options.getters || {}
        this.getters = {}
        Object.keys(getters).forEach(getterName => {
          Object.defineProperty(this.getters, getterName, {
            get: () => {
              return getters[getterName](this.state)
            }
          })
        })

    3、mutations

    (1)使用

    //store.js
    export default new Vuex.Store({
            // 相当于methods
        mutations: {
            // mutations内部的函数,天生具备一个形参
            add(state, n) {
                state.count += n;
            },
            decrease(state, n) {
                state.count -= n;
            }
        },
    }
    methods: {
                submit() {
                    console.log('success');
                },
    
                // 解构仓库mutations里面的方法,要啥解构啥
                ...mapMutations(['add', 'decrease']),
                // this.$store.commit('add'),
                    
                ...mapActions(['addAction', 'decreaseAction']),
                // this.addAction()调用actions里面的方法
                // this.$store.dispatch('add'),
    }

    (2)注意

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler) 。这个回调函数就是进行状态更改的地方,并且它会接受 state 作为第一个参数,不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法

    可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload) ,在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读

    (3)实现

       // mutations
        let mutations = options.mutations || {}
        this.mutations = {}
        Object.keys(mutations).forEach(mutationName => {
          this.mutations[mutationName] = (arg) => {//保证多个(第二个)参数的传入
            mutations[mutationName](this.state, arg)
          }
        })
        
        commit = (method, arg) => {//使用箭头函数改变被调用的this的指向
            // console.log(this);
            this.mutations[method](arg)
      }

    4、actions

    (1)使用

    //store.js
    export default new Vuex.Store({
        actions: {
            addAction(context) {
                // 在这里调用add方法
                context.commit('add', 10);
            },
            decreaseAction({
                commit
            }) {
                commit('decreaseAction', 5)
            }
        },
    }

    (2)注意

    (3)实现

        // actions
        let actions = options.actions || {}
        this.actions = {}
        Object.keys(actions).forEach(actionName => {
          this.actions[actionName] = (arg) => {
            actions[actionName](this, arg)
          }
        })
        
       dispatch=(method, arg) =>{
        this.actions[method](arg)
      }

    5、modules

    (1)使用

        // actions
        let actions = options.actions || {}
        this.actions = {}
        Object.keys(actions).forEach(actionName => {
          this.actions[actionName] = (arg) => {
            actions[actionName](this, arg)
          }
        })
        
       dispatch=(method, arg) =>{
        this.actions[method](arg)
      }
        //store.js
        modules: {
            extra: extra
        }

    (2)注意

    三、整体代码

    let Vue = null//全局的_Vue
    
    class Store {
      constructor (options) {
        // state
        //this.state = options.state 写法不完美,当改变数据的时候,不能动态的渲染,所以需要把data中的数据做成响应式的
        this.vm = new _Vue({
          data: {
            state: options.state//data中的数据才是响应式
          }
        })
    
        // getters
        let getters = options.getters || {}
        this.getters = {}
        Object.keys(getters).forEach(getterName => {
          Object.defineProperty(this.getters, getterName, {
            get: () => {
              return getters[getterName](this.state)
            }
          })
        })
    
        // mutations
        let mutations = options.mutations || {}
        this.mutations = {}
        Object.keys(mutations).forEach(mutationName => {
          this.mutations[mutationName] = (arg) => {//保证多个(第二个)参数的传入
            mutations[mutationName](this.state, arg)
          }
        })
    
        // actions
        let actions = options.actions || {}
        this.actions = {}
        Object.keys(actions).forEach(actionName => {
          this.actions[actionName] = (arg) => {
            actions[actionName](this, arg)
          }
        })
    
      }
    
      dispatch=(method, arg) =>{
        this.actions[method](arg)
      }
    
      commit = (method, arg) => {
        // console.log(this);
        this.mutations[method](arg)
      }
    
      get state() {
        return this.vm.state
      }
    
    }
    
    let install = function(_Vue) {
      Vue = _Vue
      Vue.mixin({
        beforeCreate() {//在组件创建之前自动调用,每个组件都有这个钩子
          if (this.$options && this.$options.store) {  // this.$options读取到根组件
            this.$store = this.$options.store
          } else { // //如果不是根组件的话,也把$store挂到上面,因为是树状组件
            this.$store = this.$parent && this.$parent.$store
          }
        }
      })
    }
    
    let Vuex = {
      Store,
      install
    }
    
    export default Vuex

    (学习视频分享:vuejs入门教程编程基础视频

    以上就是深入了解vuex的实现原理的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:javascript vuex
    上一篇:快速搞懂Vue2 diff算法(图文详解) 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • vuex有什么属性• 通俗易懂!详解VUEX状态仓库管理• Vuex状态管理之Mutation的使用详解• 聊聊Vuex与Pinia在设计与实现上的区别• 浅析集中式状态管理Vuex的使用方法• 聊聊vuex如何模块化编码+命名空间• 聊聊两个Vue状态管理库Pinia和Vuex,该用哪个?
    1/1

    PHP中文网