• vuex


    # 手写 vuex

    ## 第1步, 先安装vuex, 并介绍vuex的基本使用

    ### 1.安装:
    yarn add vuex 

    ### 2.新建 store/index.js 
    ```js
    import Vue from 'vue';
    import vuex from 'vuex';

    // 1.Vue.use(Vuex);  Vuex是一个对象 install方法
    // 2.Vuex中有一个Store类 
    // 3.混入到组件中 增添store属性

    Vue.use(vuex);

     const store = new vuex.Store({
        state: { // -> data
            age: 10,
        },
        getters: { // 计算属性
            mumAge(state) {
                return state.age + 30
            }
        },
        mutations: { // method=> 同步的更改state  mutation的参数是状态
            changeAge(state, payload) {
                state.age += payload; // 更新age属性
            }
        },
        actions: { // 异步操作做完后将结果提交给mutations
            changeAge({ commit }, payload) {
                setTimeout(() => {
                    commit('changeAge', payload)
                }, 1000);
            }
        },
    });

    export default store;
    ```

    ### 3.在入口文件挂载
    ```js
    import store from './store/index.js';

    new Vue({
      router,
      store, // 每个子组件,都会拥有一个属性$store
      render: h => h(App)
    }).$mount('#app')
    ```
    ###  4.在根组件app.vue里演示

    ```html
    <template>
        <div id="app">
        Alice今年多少岁:{{$store.state.age}}
        <br />
        妈妈的年龄是:{{$store.getters.mumAge}}
        <br />

    <p>
       <button @click="$store.commit('changeAge',5)">同步更新age</button>
        <button @click="$store.dispatch('changeAge',8)">异步更新age</button>
    </p>
       
      </div>

    </template>
    ```

    ## 第2步- 新建自己的插件包 vuex, 并添加相关配置

    新建vuex/index.js
    ```js
    let Vue;

    // Vue.use 方法会调用插件的install方法,此方法中的参数就是Vue的构造函数
    // Vue.use = function (plugin) {
    //     plugin.install(this);
    // }

     const install = (_vue)=>{
          // _Vue 是Vue的构造函数
        Vue = _vue;

        // 需要将根组件中注入的store 分派给每一个组件 (子组件) 通过Vue.mixin

        Vue.mixin({ // 内部会把生命周期函数 拍平成一个数组 
            beforeCreate:function(){
                   // 给所有的组件增加$store 属性 指向我们创建的store实例
                   console.log(this.$options.name);
                    const options = this.$options; // 获取用户所有的选项
                    if(options.store){ // 根实例
                        this.$store = options.store;
                    }else if(options.parent && options.parent.$store){ // 儿子 或者孙子....
                        this.$store = options.parent.$store;
                    }  
            },
        });
        
    }
     const Store = ()=>{

    }
    // Vuex.Store  Vuex.install

    export default {
        install,
        Store
    }

    // 这个文件是入口文件,核心就是导出所有写好的方法
    ```

    在store/index.js修改 

    ```js
    // import vuex from 'vuex'; //插件里的
    import vuex from '../../vuex/index.js'; //自己插件里的
    ```

    ## 第3步 开始写install方法

    vuex/store.js

    ```js
    let Vue;
    export const install = (_vue)=>{
        // _Vue 是Vue的构造函数
      Vue = _vue;

      // 需要将根组件中注入的store 分派给每一个组件 (子组件) Vue.mixin

      Vue.mixin({ // 内部会把生命周期函数 拍平成一个数组 
          beforeCreate:function(){
                 // 给所有的组件增加$store 属性 指向我们创建的store实例
                 console.log(this.$options.name);
                  const options = this.$options; // 获取用户所有的选项
                  if(options.store){ // 根实例
                      this.$store = options.store;
                  }else if(options.parent && options.parent.$store){ // 儿子 或者孙子....
                      this.$store = options.parent.$store;
                  }  
          },
      });
      
    }
    ```


    ## 第4步 处理store实例属性 state 

    ```js
    export class Store{  //容器的初始化
        constructor(options){  //options 就是你new vuex.Store(state,getters,mutations,actions)
            //方式一
            const state = options.state;  //数据变化要更新视图(核心逻辑)
            // 通过new Vue({data})  - >响应式数据

             // 1. 添加状态state逻辑, 数据在哪使用, 就会收集对应的依赖
             this._vm = new Vue({
                data:{ //属性如果是$开头的, 默认不会将这个属性挂载在vm上
                    $$state:state  //会将$$state对应的对象 都通过defineProperty来进行属性劫持
                },
                computed:computedData
            });
        }
        get state(){   //属性访问器,通过 new Store().state 访问 Object.defineProperty(Store,state:{})
            return this._vm._data.$$state;
        }
    }

    ```

    ```js
    //方式二:通过Vue.util.defineReactive方法, 将state 挂载在 Store的实例化对象this上, 初始值为options.state
     Vue.util.defineReactive(this, 'state', options.state) ; 
    ```

    ## 第5步 处理store实例属性 getters

    ```
    // 2. 处理getters属性, 具有缓存的 computed带有缓存(如果值不变,多次取值不会重新赋值)
     this.getters = {};

    Object.keys(options.getters).forEach((key)=>{
            Object.defineProperty(this.getters,key,{
                get:()=>options.getters[key](this.state,options.getters)
            })
    });

    ```

    优化:封装成一个函数

    ```js
    //遍历对象
    export const forEachValue = (obj,callback) =>{
        Object.keys(obj).forEach(key=>{
            callback(obj[key],key)
        });
    }
    ```

    使用:
    ```js
    forEachValue({a:1},function(value,key){
        // key - > a 
        // value - >1
    })
    ```

    把处理getters的代码使用封装的函数后 就是这样 

    ```js
    forEachValue(options.getters,(fn,key)=>{
        Object.defineProperty(this.getters,key,{
            get:()=>fn(this.state)
        })

    }) 
    ```
    但是这样有一个问题, getter不具有缓存的作用

    添加测试
    ```
    getters: { // 计算属性
            mumAge:function(state) {
                console.log(1111);
                return state.age + 30;
            }
        },
    ```
    每次回重复执行 

    **处理缓存问题**

    ```js
     const computedData = {};

     forEachValue(options.getters,(fn,key)=>{
                //添加计算属性
        computedData[key] = ()=>{  //将用户的getters定义在实例上
            return fn(this.state);
        }
        Object.defineProperty(this.getters,key,{
            
            // get:()=>{ return fn(this.state);} // 1. 这种不好,每次都要调用
            get:()=>{ return this._vm[key]; }//2. 从实例属性上取,读缓存
        })
     });

      // 1. 添加状态state逻辑, 数据在哪使用, 就会收集对应的依赖
        this._vm = new Vue({
            data:{ //属性如果是$开头的, 默认不会将这个属性挂载在vm上
                $$state:state  //会将$$state对应的对象 都通过defineProperty来进行属性劫持
            },
            computed:computedData
        });


    ```

    ## 第6步 处理store实例属性 mutations

    ```js
    // 3. 实现mutations
    this.mutations = {};
    // mutations: {
    //     changeAge:fn1,
    //     getAge:fn2
    // }
    forEachValue(options.mutations,(fn,key)=>{
        this.mutations[key] = (payload)=>{
            fn(this.state,payload); //第一个参数是state
        }
    })
    ```

    通过提交一个 commitation函数修改state

    需要给构造函数store添加一个原型方法

    ```js
     // commit = function(){}
        commit=(type,playload)=>{  //使用箭头函数的目的:保证当前的this 指向当前的store实例
            // 调用commit就是去调用配置好的mutations函数
            this.mutations[type](playload);
        }
    ```
    ## 第7步 处理store实例属性 actions

    ```js
    // 4.实现actions
        this.actions = {};
        forEachValue(options.actions,(fn,key)=>{
            this.actions[key] = (payload)=>{
                fn(this,payload); //第一个参数是store的实例
            }
        })
    ```
    有异步数据更新的时候, 通过dispath一个action
    需要给构造函数store添加一个原型方法

    ```
     dispatch=(type,playload)=>{
            this.actions[type](playload);
        }
    ```


    ## 扩展 Vue.util.defineReactive方法 

    ```js
    // 第二种写法, 主要是体现在state的监测性 和 getters的 缓存处理方式上
    export class Store{
        constructor(options){
            //方式二:通过Vue.util.defineReactive方法, 将state 挂载在 Store的实例化对象this上, 初始值为options.state
            //第一步:state
            Vue.util.defineReactive(this, 'state', options.state) ; 
            // state,getters,mutations,actions
            // this.state = {};
            this.getters = {};
            this.mutations = {};
            this.actions = {};

            //第二步:getters
            forEachValue(options.getters,(fn,key)=>{
                // var newArr = {}; //缓存用
                // debugger
                // if(newArr[key] && newArr[key]==fn(this.state)){
                //     return fn(this.state);
                // } else {
                //     newArr[key] = fn(this.state)
                // } 

                Object.defineProperty(this.getters,key,{
                    //触发get, 读取属性的值, 这里没有实现缓存的功能
                    get:()=>{
                         return fn(this.state)
                        //  return newArr[key]
                     },
                    //  set:()=>{
                    //     newArr[key] = fn(this.state)
                    //  }
                })
            });

            // 第三步:mutations 
            // mutations: {
            //     changeAge:fn1,
            //     getAge:fn2
            // }
            forEachValue(options.mutations,(fn,key)=>{
                this.mutations[key] = (payload)=>{
                    fn(this.state,payload); //第一个参数是state
                }
            })

            // 4.实现actions
            forEachValue(options.actions,(fn,key)=>{
                this.actions[key] = (payload)=>{
                    fn(this,payload); //第一个参数是store的实例
                }
            })

        }

          // commit = function(){}  
          //type:某个mutation函数
          commit=(type,playload)=>{  //使用箭头函数的目的:保证当前的this 指向当前的store实例
            // 调用commit就是去调用配置好的mutations函数
            this.mutations[type](playload);
        }
        //type:某个action函数
        dispatch=(type,playload)=>{
            this.actions[type](playload);
        }
    }

    ```








  • 相关阅读:
    javascript命名规范
    angularjs指令参数transclude
    angular中的compile和link函数
    angularjs中的directive scope配置
    sublime text3同时编辑多行
    jquery中on/delegate的原理
    defered,promise回顾
    导航栏滚动到顶部后固定
    angularjs揭秘
    $stateParams
  • 原文地址:https://www.cnblogs.com/laneyfu/p/13680541.html
Copyright © 2020-2023  润新知