手写Vuex源码

作者: JX灬君 | 来源:发表于2021-08-29 21:27 被阅读0次

    Vuex源码实现

    1. Vuex核心概念State,Getters,Mutations, Actions, Modules

    • State 存放数据,(响应式:数据改变,视图也改变)

      age: 18

    • Getters 相当于Vue中计算属性,具有缓存机制

      changeAge:state=>{ return state.age + 2} // 将age+2

    • Mutations 修改数据(同步)

      addAge: (state, data) => { state.age += data }

      @click="$store.commit('addAge', 5)"

    • Actions 修改数据(异步)

      // actions:{}
      awaitAddAge: ({ commit }, data) => { 
          setTimeout(() => {
            commit('addAge', data)
          }, 2000);  
        }
      
      // 页面使用 延迟两秒执行
      @click="$store.dispatch('awaitAddAge', 2)"
      
    • Modules

    Modules模块化可以理解为Store里嵌套Store

    2. 手写Vuex分析

    • 分析:

      • Vuex首先用到了单例模式和发布订阅者模式两种设计思想

        • 单例模式,即Vuex的四个核心概念都在一个Store实例中完成
        • 发布订阅者模式:即mutations同步修改方法,和actions异步修改方法通过发布订阅者模式来实现state数据的修改。
      • Vuex是个插件,通过vue.use产生关联(一般通过vue.use注册插件)

          1. Vue.use(fnc) 执行方法

            function ac() { 
              console.log(1000);
            }
            Vue.use(ac) // 打印1000
            
          1. 如果这个方法中有一个install这个属性(方法),会执行install

            function ac() { 
              console.log(1000);
            }
            ac.install = function () {  
              console.log(300);
            }
            Vue.use(ac)
            
            Vue.use(Vuex); // 打印300,1000不打印
            
          1. 如果install有参数,第一个参数就是Vue的实例

            function ac() { 
              console.log(1000);
            }
            ac.install = function (_Vue) {  
              console.log(_Vue);
            }
            Vue.use(ac) // 打印出Vue的实例
            
      • 每一个使用的组件中都有store容器

      • Mutations通过Commit方法直接修改State

      • Actions通过Dispatch触发Commit执行Mutations方法

      • Vuex是个单线程的方法


        image.png
      • 写的每个方法要在每一个组件中使用,需要把方法放在根实例下。

      • 组件的渲染关系是父子关系

    3. 创建Vuex入口文件

    在src目录下创建Vuex文件夹并新建index.js(Vuex入口文件)(替换Vuex文件)

    // 入口文件
    import { Store, install } from './install'
    export default {
      Store, // 容器
      install // 注册插件,与Vue产生关联
    }
    
    • 创建install.js文件

      export class Store {
      
      }
      export const install = function () {  
        console.log(200);
      }
      
    • 替换官方Vuex为新建的Vuex插件,修改store/index.js

      // old 
      import Vuex from "vuex";
      // new 修改为
      import Vuex from "../Vuex"; // 自定义Vuex组件
      
    • 刷新页面,打印了200

      通过store/index.js Vue.use(Vuex) 调用install方法,执行install.js文件里的console.log(200);,打印出200

    4. 将store放到每个实例上

    获取Vue实例并把store放到每一个组件实例上

    // 实现 store 放到每一使用的组件中
    export const install = function (_Vue) {  
      Vue = _Vue // 获取Vue实例,以便使用Vue实例上的方法
      // 使用Vue提供的方法 Vue.mixin({})
      // Vue.mixin({})方法,混入方法和数据
      Vue.mixin({ // mixin 混入生命周期beforeCreate
        beforeCreate () {
          let options = this.$options
          if (options.store) { // 根实例,只有根实例才有store
            // 给根实例添加一个$store属性
            this.$store = options.store
          } else {   // 其他实例,除根实例外
            // 判断有没有父亲实例,如果有拿到父亲实例上的$store
            // 从根实例开始,逐步从根实例传到子组件实例上
            this.$store = this.$parent && this.$parent.$store
          }
          // console.log(this.$store); 
        }
      })
    } 
    
    • 设置Store容器

      export class Store { // Store容器 
        constructor(options){ // options为用户配置项(包括state,getters,mutations等等)
          console.log(options);
        }
      }
      
    • 初始化Vuex.Store

      export default new Vuex.Store({
        state: state, // eslint-disable-next-line
        getters: getters,
        mutations: mutations,
        actions: actions,
        modules: {},
      });
      
    • 取值

      $store.state.age = 33 // 成功获取

    • 修改

      @click="$store.state.age = 66" // 页面未刷新,但是实例上age已变成66,也就是state.age未实现响应式。

    5. 处理Vuex响应式

    • 处理Vuex响应式

      this.state = options.state 处理不能实现响应式

      处理Vuexstate响应式,用Vue的数据劫持来实现

      将state上的所有属性代理到_vm实例上, 然后在get时,返回代理的_vm实例

      export class Store { // Store容器 
        constructor(options){ // options为用户配置项(包括state,getters,mutations等等)
          // this.state = options.state // 不能实现响应式
          this._vm = new Vue({ // 通过Vue实例来对state进行代理
            data: { 
              state: options.state
            }
          })
          // Object.defineProperty() // Object.defineProperty方法可以在类里进行劫持
        }
        get state() {
          return this._vm.state // 返回实现代理的_vm实例
        }
      }
      

    6. 处理计算属性getters

    • 获取options里的getters内容
    • 如果getters里有多个方法需遍历
    • 通过Object.defineProperty()方法对getters方法进行劫持
     // getters 计算属性,用户 
        let getters = options.getters
        this.getters = {}
    
        console.log(getters['getAge'](this.state));
        // 当getters里有多个方法时,对方法进行遍历
        Object.keys(getters).forEach(key => {
          // 如果this.getters没有getters方法,则进行添加
          Object.defineProperty(this.getters,key,{
            get: () => {
              console.log(getters[key]);
              console.log(this.state);
              // getters[key] 获取到到是getters里到方法,
              // 通过getters[key](this.state)传入this.state调用这个方法
              return getters[key](this.state) 
            }
          })
        })
    

    7. 创建方法文件夹utils

    • 创建utils文件夹,并新建index.js文件

    • 新建第一个方法foreach

      export function foreach(obj, cb){
          Object.keys(obj).forEach(key => {
            cb(key, obj[key])
          })
      } 
      
    • 导入foreach方法实现getters

      // 调用foreach方法实现getters
          foreach(getters,(key, value) => {
            Object.defineProperty(this.getters,key,{
              get: () => {
                // console.log(getters[key]);
                // console.log(this.state);
                // getters[key] 获取到到是getters里到方法,
                // 通过getters[key](this.state)传入this.state调用这个方法
                return value(this.state) 
              }
            })
          })
      

    8. 设置getters缓存机制

    • 通过Vue的计算属性来代理实现getters方法的缓存机制

    • 在Vue中,state响应式和getters缓存机制都是靠初始化一个Vue实例来实现,响应式通过Vue实例的data数据劫持来实现,getters缓存机制通过Vue实例的计算属性computed来实现。

      let computed = {} // 定义一个computed对象
      // 在foreach方法中添加computed方法
      computed[key] = () => {
          return value(this.state) 
      }
      // 通过Object.defineProperty()方法的get属性返回当前Vue实例_vm
      Object.defineProperty(this.getters,key,{
        get: () => {
          // console.log(getters[key]);
          // console.log(this.state);
          // getters[key] 获取到到是getters里到方法,
          // 通过getters[key](this.state)传入this.state调用这个方法
          console.log(this._vm);
          return this._vm[key] 
          // return value(this.state)
        }
      })
      
      // 在初始化Vue实例时,代理computed
      this._vm = new Vue({ // 通过Vue实例来对state进行劫持
        data: { 
          state: options.state
        },
        computed // ES6中属性和属性值相同可以直接省略  computed = computed:'computed'
      })
      

    9. mutations方法实现

    • 首先明确知道 mutations和actions 是个发布订阅者模式

    • 在Store类的constructor构造方法里初始化mutations方法

      // 获取store里设置的mutations方法
          let mutations = options.mutations
          // 初始化一个mutations空对象
          this.mutations = {}
          // 遍历Store里设置的mutations方法
          foreach(mutations, (key, value) => {
            this.mutations[key] = (data) => {
              value(this.state, data)
            }
          })
      
    • 在Store类上通过commit方法调用mutations方法

      可以理解为通过commit方法专门来调用对应的mutations方法

      commit = (name, data) => { // this 永远指向store,当前的实例
         this.mutations[name](data)
      }
      

    10. actions方法实现

    • actions方法与mutations方法基本一致,只有在Store类里设置actions方法时,传入方法的第一个参数是this,也就是当前Store这个类的实例。

    • 在Store类的constructor构造方法里初始化actions方法

      // 获取用户设置的acitons异步修改方法
          let actions = options.actions
          // 初始化一个空的actions对象
          this.actions = {}
          // 遍历用户设置的acitons方法并初始化执行方法
          foreach(actions, (key, value) => {
            this.actions[key] = (data) => {
              value(this, data) // 此处应传入this即Store类这个实例
            }
          })
      
    • 在Store类上通过dispatch方法调用actions方法

      // actions异步修改数据方法
        dispatch = (name, data) => {
          this.actions[name](data)
        }
      
    • dispatch方法和commit方法在使用时有差别

      // commit同步修改方法在使用时第一个参数直接传入当前Store的state
      addAge: (state, data) => { state.age += data }
      // dispatch异步修改方法在使用时第一个参数传入的是Store这个实例,dispatch在使用时会从Store实例中解构出commit同步修改方法
      awaitAddAge: ({ commit }, data) => { 
          setTimeout(() => {
            commit('addAge', data)
          }, 2000);  
      }
      

    11. modules模块化实现

    • modules模块化处理需要格式化数据
    • 格式化数据 变成一个树形结构,也就是一个对象
      // 页面结构
      export default new Vuex.Store({
      state: state, // eslint-disable-next-line
      getters: getters,
      mutations: mutations,
      actions: actions,
      // 模块  {mutations: []}
      modules: {
        a: {
          state:{
            age: 200
          },
          mutations:{
            addAge: (state, data) => { state.age += data }
          }
        },
        b : {
          state:{
            age: 112
          },
          mutations:{
            addAge: (state, data) => { state.age += data }
          }
        }
      },
    });
    // 转换成的结构
    root = {
      _raw: '用户传过来的数据',
      _children:{
        a: {
          _raw: 'a用户传过来的数据',
          _children:{
    
          },
          state: '数据'
        }
        b: {
          _raw: 'b用户传过来的数据',
          _children:{
    
          },
          state: '数据'
        }
      },
      state: '根数据'
    }
    

    相关文章

      网友评论

        本文标题:手写Vuex源码

        本文链接:https://www.haomeiwen.com/subject/mymgiltx.html