vuex的实现——使用插件及Mixin混入添加全局状态管理(二)
这一节主要是介绍如何使用插件及混入开发全局单例模式管理状态。通过根组件注入这个全局单例对象,使得后代组件能够直接读取状态。一、options的使用。options选项可以往组件中添加自定义属性。并可通过this.$options.xxx访问。我们在main.js定义并实例化一个Store类。通过options注入到根组件中,这样后代组件都能够通过this.$options.xxx访问到状态。...
这一节主要是介绍如何使用插件及混入开发全局单例模式管理状态。通过根组件注入这个全局单例对象,使得后代组件能够直接读取状态。
一、options的使用。options选项可以往组件中添加自定义属性。并可通过this.$options.xxx访问。我们在main.js定义并实例化一个Store类。通过options注入到根组件中,这样后代组件都能够通过this.$options.xxx访问到状态。
main.js代码:
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
class Store {
constructor (options = {}) {
this.state = options.state || {}
}
}
const state = {
count: 1
}
const store = new Store({ state })
new Vue({
el: '#app',
store,
components: { App },
template: '<App/>'
})
然后在main.js中我们可以通过this.$options.store.state.count来访问共享的状态。
在App.vue中,我们可以通过this.$options.parent.$options.store.state.count来访问共享状态。
App.vue源码:
<template>
<div id="app">
app.vue
<Counter/>
</div>
</template>
<script>
import Counter from './components/counter'
export default {
components: {
Counter
},
mounted () {
console.log(this.$options.parent.$options.store.state.count)
}
}
</script>
在counter.vue中,我们可以通过this.$options.parent.$options.parent.$options.store.state.count访问共享状态。
counter.vue源码:
<template>
<div id="counter">
counter.vue
<CounterItem/>
</div>
</template>
<script>
import CounterItem from './counterItem'
export default {
components: {
CounterItem
},
mounted () {
console.log(this.$options.parent.$options.parent.$options.store.state.count)
}
}
</script>
在counterItem.vue中,我们可以通过this.$options.parent.$options.parent.$options.parent.$options.store.state.count来访问共享的状态。通过options,我们可以在任何组件中直接访问到共享的状态,而不需要一层一层从根组件往下传。这样如果App.vue没有使用到count,那么就不需要在App.vue中写任何关于count的代。但是,这个也带来了一个问题,通过this.$options.parent.xxxxx这种方式访问,写法繁琐并且难以阅读。如果组件树层级很深,那么这样写并不比一层一层传递props简便。那有没有什么方法可以在任何组件中都可以直接通过类似于this.$store.state的方式访问呢?比如,如果我们能够在每个组件初始化时,都把this.$options.xxx.store注入到this.$store中,那么我们就可以直接在组件中通过this.$store访问到共享状态,而不需要写一大串的名称。
二、Vue.mixin。全局混入。在Vue.js的api文档中,我们可以看到有这么一段话:全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。插件作者可以使用混入,向组件注入自定义的行为。不推荐在应用代码中使用。也就是说我们可以通过Vue.mixin来向所有组件自定义的行为。那么我们是不是可以在每个组件初始化的时候,将this.$options.xxx.store注入到this.$store属性中呢?往main.js中添加如下代码:
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
Vue.mixin({ beforeCreate: vuexInit })
这里我们使用Vue.mixin为每个组件全局注入了自定义行为,即在每个组件实例化后调用beforeCreate(vue组件生命周期钩子函数)方法。我们给beforeCreate传入了自定义的方法vuexInit。这个方法就是为了实现将this.$options.xxxx.store注入到每个组件的this.$store属性中。
在main.js实例化后,执行vuexInit方法,if(options.store)为真,this.$store 为全局单例对象store。
在App.vue实例化后,执行vuexInit方法,if(options.store)为假,执行第二个语句块this.$store = options.parent.$store。options.parent其实就是main.js组件实例,因此options.parent.$store就是store。这样我们就将store注入到App.vue组件中的this.$store属性中,我们现在可以在App.vue中直接通过this.$store.state.count来访问到共享状态了。
在counter.vue实例化后,执行vuexInit方法。if(options.store)为假,执行第二个语句块,options.parent为App.vue实例,因此options.parent.store将store注入到this.$store,即counter.vue组件实例中。同理,我们现在也可以在counter.vue中直接通过this.$store.state.count访问全局共享状态了。
到这里,我们已经可以全局为每个组件注入this.$store,达到直接访问全局状态的目的。这也是vuex源码中mixin.js的核心代码。
三、插件化。现在的main.js混杂了太多的逻辑,我们需要把store剥离出来。Vue.js 的插件应当有一个公开方法 install
。使用Vue.use安装 Vue.js 插件。如果插件是一个对象,必须提供 install
方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。当 install 方法被同一个插件多次调用,插件将只会被安装一次。也就是说当我们使用Vue.use安装插件时,会自动调用插件里面的install方法,因此我们可以在install方法里调用Vue.mixin全局混入this.$store。
新建mixin.js文件,代码如下:
export default function (Vue) {
Vue.mixin({ beforeCreate: vuexInit })
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
// 注意这里的options.store(),有时候为了模块重用,以及避免状态单例,可以使用工厂函数
// 返回store实例。参考https://ssr.vuejs.org/zh/guide/structure.html
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
新建store.js文件,代码如下:
import applyMixin from './mixin'
let Vue
class Store {
constructor (options = {}) {
this.state = options.state || {}
}
} // Store
function install (_Vue) {
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
export default {
install,
Store
}
修改main.js文件为:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import MyVuex from './store.js'
Vue.use(MyVuex)
Vue.config.productionTip = false
const state = {
count: 1
}
const store = new MyVuex.Store({
state
})
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
components: { App },
template: '<App/>'
})
这样我们就把逻辑剥离出来了,现在还没有往里面添加改变store状态的方法。
四、给store添加响应式属性。现在组件通过this.$store.state.count访问到共享的状态,然而现在的store并不能响应数据变化。在counterItem.vue中添加一个方法,改变count的值: this.$store.state.count ++。此时count的值改变了,然而视图却没有相应地刷新。例如,我们在App.vue中通过计算属性访问count,如:
<template>
<div id="app">
app.vue
<div>{{ count }}</div>
<Counter/>
</div>
</template>
<script>
import Counter from './components/counter'
export default {
components: {
Counter
},
computed: {
count () {
return this.$store.state.count
}
}
}
</script>
在counterItem.vue中添加改变count的方法,如:
<template>
<div id="counter-item">
counter-item
<div @click="increment">increment</div>
</div>
</template>
<script>
export default {
methods: {
increment () {
this.$store.state.count++
console.log(this.$store.state.count)
}
}
}
</script>
点击increment按钮,发现console.log输出的state值已经改变了,然而视图却没有刷新。
我们知道vue实例中,data中的属性能够响应数据的变化并能够将变化响应到后代组件,因此我们可以在store中构造一个vue实例,将store的state状态存到该vue实例的data属性中。修改store.js如下:
import applyMixin from './mixin'
let Vue
class Store {
constructor (options = {}) {
const state = options.state || {}
this._vm = new Vue({
data: {
$$state: state
}
})
}
get state () {
return this._vm._data.$$state
}
} // Store
function install (_Vue) {
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
export default {
install,
Store
}
此时点击counterItem中的increment,可见视图刷新了,count的值也改变了。
现在已经实现了一个简单的store了,并且能够响应数据的变化。现在还没有往store里添加操作全局状态的方法。显然在每个组件中通过this.$store.state.count是不科学的。这种操作无异于直接操作全局变量,不利于debug。
下一节我会继续往Store类里面添加操作方法。
未完待续。。。。。。
更多推荐
所有评论(0)