miniVuex

11/18/2020 vuexminivue

# VueX 原理

  • 利用的是 vue 的数据响应
  • 对 state 进行代理,并在更新时触发对应的依赖
  • 封装 commit 控制更新 state 只能通过 mutation 从而监控数据流向
  • 保证状态以一种可预测的方式发生变化
  • 提供一系列工具函数

# mini 实现效果

# mini 实现代码

实现较为简单,并且只做了 一层 未做命名空间

# 使用

与正常的 Vuex 基本一致,不过不能使用命名空间模块化 mini 嘛 简单处理一下

index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <!-- <script src="https://unpkg.com/vuex"></script> -->
  <title>miniVuex</title>
</head>

<body>
  <div id="app">
    <h2>{{title}}</h2>
    <hr>
    <Demo1></Demo1>
    <hr>
    <Demo2></Demo2>
  </div>
</body>
<script src="./vuexUtils.js"></script>
<script src="./miniVuex.js"></script>
<script src="./components.js"></script>
<script src="./store.js"></script>
<script src="./app.js"></script>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
components.js
const Demo1 = {
  template: `<div>
  <h4>使用$state commit  dispatch</h4>
  {{$store.getters.key1}}
  <div>key1:{{$store.state.key1}}</div>
  <button @click='commit'>Commit Key1</button>
  <button @click='dispach'>dispach Key1</button>
  </div>`,
  methods: {
    commit() {
      this.commitCount = this.commitCount || 0
      this.commitCount++
      this.$store.commit('setKey1', `CommitKey1:${this.commitCount} Times`)
    },
    dispach() {
      this.dispatchCount = this.dispatchCount || 0
      this.dispatchCount++
      this.$store.dispatch('setKey1', `dispatch1:${this.dispatchCount} Times`)
    }
  },
}

const { mapGetters: mapG, mapActions: mapA, mapMutations: mapM } = Vuex
const Demo2 = {
  template: `<div>
  <h4>使用 mapGetters mapActions mapMutations</h4>
  <div>key1:{{key1}}</div>
  <div>key2:{{key2}}</div>
  <button @click='setKey1("mapMutations")'>Commit Key1</button>
  <button @click='setKey2("mapActions")'>dispach Key2</button>
  </div>`,
  computed: {
    ...mapG(['key1', 'key2'])
  },
  methods: {
    ...mapM(['setKey1']),
    ...mapA(['setKey2'])
  },
}


Vue.component('Demo1', Demo1)
Vue.component('Demo2', Demo2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
store.js
const store = new Vuex.Store({
  state: { key1: 'key1', key2: 'key2' },
  mutations: {
    setKey1(state, v) {
      state.key1 = v
    },
    setKey2(state, v) {
      state.key2 = v
    }
  },
  getters: {
    key1(state) {
      return state.key1
    },
    key2(state) {
      return state.key2
    },
  },
  actions: {
    setKey1(store, data) {
      setTimeout(() => {
        store.commit('setKey1', data)
      }, 1000)
    },
    setKey2(store, data) {
      setTimeout(() => {
        store.commit('setKey2', data)
      }, 1000)
    }
  },
  plugins: [logPlug]
})


// 插件,可以用于 持久化等操作

function logPlug(store) {
  store.subscribe((store, type) => {
    console.log('logger---start')
    console.log(type)
    console.log('当前快照')
    console.log(store)
    console.log('logger---end')
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
app.js

const app = new Vue({
  el: "#app",
  store,
  data: {
    title: "Vuex"
  }
})
1
2
3
4
5
6
7

# mini 源码部分

vuexUtils.js

function mapGetters(keys = []) {
  return keys.reduce((r, key) => {
    r[key] = function () {
      return this.$store.getters[key]
    }
    return r
  }, {})
}

function mapState(keys = []) {
  return keys.reduce((r, key) => {
    r[key] = function () {
      return this.$store.state[key]
    }
    return r
  }, {})
}
function mapActions(keys) {
  return keys.reduce((r, key) => {
    r[key] = function (payload) {
      return this.$store.dispatch(key, payload)
    }
    return r
  }, {})
}

function mapMutations(keys) {
  return keys.reduce((r, key) => {
    r[key] = function (payload) {
      return this.$store.commit(key, payload)
    }
    return r
  }, {})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
miniVuex.js
class Store {
  constructor(options) {
    // 尝试自动install一次,如果在浏览器环境  不需要用户手动 use
    if (typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
    this.$options = options
    const { state, getters, actions, mutations, plugins = [] } = this.$options
    this.$mutations = mutations
    this.$actions = actions
    // vuex响应式的原理  依赖于Vue  所以 其他框架不能使用vuex
    this.$vm = new Vue({ data: state })
    this.getters = {}
    this.handleGetters(getters)
    this._subscribe = []
    // 处理插件  插件注册的回调函数会在每次commit之后执行
    plugins.forEach(plugin => plugin(this))
  }
  get state() { return this.$vm }
  // 不允许 直接修改state  只能使用commit  也就是mutation  
  // 此处只是简单处理,源码中提取了一个 withCommit统一处理
  set state(val) {
    throw new Error('不能直接赋值给state', val)
  }
  // getters特殊处理,可以使用computed  但是 一个变化触发全部更新可能不是一个好的方式
  handleGetters(getters) {
    Object.keys(getters).forEach(key => {
      Object.defineProperty(this.getters, key, {
        get: () => { return getters[key](this.state) }
      })
    })
  }
  // 订阅commit  提供给插件使用
  subscribe(fn) { this._subscribe.push(fn) }
  // 执行同步提交
  commit(method, payload) {
    let m = this.$mutations[method]
    if (m) {
      m(this.$vm, payload)
    }
    this._subscribe.forEach(f => f(this, { type: method, payload }))
  }
  // 执行异步提交
  dispatch(method, payload) {
    let m = this.$actions[method]
    if (m) {
      m(this, payload)
    }
  }
}
// 混入 
function install(Vue) {
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    }
  })

}
const Vuex = {
  Store, install, mapGetters, mapState, mapActions, mapMutations
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Last Updated: 12/11/2020, 11:27:59 AM