手写vuex(简易篇)

147 阅读1分钟

image.png

一、核心原理

  • Vuex本质是一个对象

  • Vuex对象有两个属性,一个是install方法,一个是Store这个类

  • install方法的作用是将store这个实例挂载到所有的组件上,注意是同一个store实例。

  • Store这个类拥有commit,dispatch这些方法,Store类里将用户传入的state包装成data,作为new Vue的参数,从而实现了state 值的响应式。

二丶先搭建一个vue项目

  • 可以看到vuex中主要就是引入了vuex和调用Vue.use()调用插件的方法
  • 调用Vuex类下面的Store
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

三丶重写vuex包

  • 先替换vuex中的引入路径
import Vue from 'vue'
import Vuex from './../vuex/index'
Vue.use(Vuex)
  • 引入intall方法和Strore类 image.png

四丶install方法的实现

  • 在beforeCreate中混入保证每个组件中都挂载上$store
  • 保证我们在每个组件中可以拿到$store
export let Vue;
function install(_Vue) {
    Vue = _Vue;
    Vue.mixin({
        beforeCreate(){ //this代表的是每个组件实例
            // 获取根组件上的store 将他共享给每个组件
            // 每个组件中都应该有$store
            let options= this.$options;
            if(options.store){
                // 根
                // console.log('根',options.name)
                this.$store = options.store
            }else{
                // 先保证他是一个子组件,并且父亲上有$store
                if(this.$parent && this.$parent.$store){
                    this.$store = this.$parent.$store
                }
            }
        }
    })
} 
// 父  this.$store -》 子 this.$store -》孙子 this.$store

export default install

五丶store的实现

  1. 首先创建一个Store类,并接受options的参数
class Store {
    constructor(options) {
        console.log(options)
    }
}

export default Store
//可以看到ooptions中输出的z值就是我们在new Vuex.Store()传入的参数
{
    "state": {},
    "getters": {},
    "mutations": {},
    "actions": {},
    "modules": {}
}

2.初始化state,重点:为保证数据的双向绑定原理,将数据放在data中

import { Vue } from './install'
class Store {
    constructor(options) {
        let {state,getters,mutations,actions} = options
        this._vm = new Vue({
            data:{
                $$state:state
            }
        })
    }
    //类的属性访问器
    get state(){
        return this._vm._data.$$state
    }
}

export default Store
  • 将数据放在state中,然后首页中打印出来,可以在页面中看到输入
state: {
  age:18,
  sex:'男'
},

<div id="app">
  <div>年龄:{{$store.state.age}}</div>
  <div>性别:{{$store.state.sex}}</div>
</div>

3.getters的实现

  • 如何实现getters中的缓存功能?是否考虑到computed中的数据是具有缓存的,并且根据对应数据的变化而变化,思考清楚了,那我们来实现gettters
let {state,getters,mutations,actions} = options
this.getters = {}
const computed = {}
Object.keys(getters).forEach((key) => {
    computed[key] =() => {
        return getters[key](this.state) //保证参数是state
    }
    Object.defineProperty(this.getters,key,{
        get:() => this._vm[key] //具备了缓存功能
    })
})

// 这个状态在页面渲染时需要收集对应的渲染watcher,这样状态更新才会更新视图
this._vm = new Vue({
    data:{
        $$state:state
    },
    computed
})

image.png

  • 当我们改变state中的age,页面中的值也会发生相应的变化

image.png

4.mutations和actions的实现

  • mutations和actions就是将用户传入的数据遍历挂载在Store自身属性上
  • dispatch和commit实现就是根据对应的类型,找对应的存储结果

this.mutations = {}
Object.keys(mutations).forEach(key => {
    this.mutations[key] = (payload) => mutations[key].call(this,this.state,payload)
})


this.actions ={}
Object.keys(mutations).forEach(key => {
    this.actions[key] = (payload) => actions[key].call(this,this.state,payload)
})


dispatch = (type, payload) => { 
    this.actions[type](payload)
}
commit = (type, payload) => {
    this.mutations[type](payload)
}

5.代码优化

  • 上面代码中getters和mutations和actions中都是在循环调用,于是我们提出一个共同方法
const forEach = (obj,fn)=>{
    Object.keys(obj).forEach((key)=>{
        fn(obj[key],key)
    })
}
  • 优化完成后,Store中代码
import { Vue } from './install'
// import { forEach } from './util'
const forEach = (obj,fn)=>{
    Object.keys(obj).forEach((key)=>{
        fn(obj[key],key)
    })
}
class Store { // new Vue.Store 缠身一个实例
    constructor(options) {
        // 以下这些变量都是用户传递的
        let { state, getters, mutations, actions, module, strict } = options;
        this.getters = {}; // 我再取getters属性的时候 把他代理到计算属性上
        const computed = {};
        forEach(getters, (fn, key) => {
            computed[key] = () => {
                return fn(this.state); // 为了保证参数是state
            }
            // 当我们去getters上取值 需要对computed取值
            Object.defineProperty(this.getters, key, {
                get: () => this._vm[key] // 具备了缓存的功能
            })
        });
        // ----------
        this.mutations = {};
        forEach(mutations, (fn, key) => {
            this.mutations[key] = (payload) => fn.call(this, this.state, payload);
        });

        // ------dispatch中派发的是动作,里面可以有异步逻辑,更改状态都要通过mutation,mutation是同步更改的-------
        this.actions = {}
        forEach(actions, (fn, key) => {
            this.actions[key] = (payload) => fn.call(this, this, payload);
        });
        // 这个状态在页面渲染时需要收集对应的渲染watcher,这样状态更新才会更新视图
        this._vm = new Vue({
            data: { // $符号开头的数据不会被挂载到实例上,但是会挂载到当前的_data上,减少了一次代理
                $$state: state // 状态在哪里取值,就会收集对应的依赖
            },
            computed
        });
        // 用户组件中使用的$store = this
    }
    // 类的属性访问器
    get state() { // this.$store.state => defineProperty中的get
        // 依赖于 vue的响应式原理
        return this._vm._data.$$state
    }
    dispatch = (type, payload) => { // 根据commit还是dispatch 找对应的存储结果
        this.actions[type](payload)
    }
    commit = (type, payload) => {
        this.mutations[type](payload)
    }
}
// state getters action mutation  (modules 分层)
export default Store;