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);
    }
}

```








posted @ 2020-09-16 18:08  lanyan  阅读(191)  评论(0编辑  收藏  举报