使用插件
这里以vuex使用为例,我们通过app.use(store)
引入vuex插件:
<div id="app">
<h3 @click="add">{{counter}}</h3>
</div>
<script src="http://unpkg.com/vue@next"></script>
<script src="http://unpkg.com/vuex@next"></script>
<script>
const { createApp, reactive, computed } = Vue
const { createStore, useStore } = Vuex
const app = createApp({
setup() {
const store = useStore()
const counter = computed(() store.state.counter)
const add = () {
store.commit('add')
}
return {
counter, add
}
},
})
const store = createStore({
state: {
counter: 1
},
mutations: {
add(state) {
state.counter++
}
}
})
app.use(store)
app.mount('#app')
</script>
源码剖析
看了vuex
引入,我有个疑问,app.use()
这个方法都做了什么事情?
我想到这个store
要在所有组件中都能访问到,即this.$store
,那么在app.use()
内部肯定有个操作就是设置全局属性:app.globalProperties.$store = store
,文档中说vue插件一定要实现一个install
方法,这个目的就很明显了,app.use()
会调用store
提供的这个install
方法,并把app
传给它。
下面去源码中验证一下:
添加插件:app.use()
packages/runtime-core/src/apiCreateApp.ts
const app: App = (context.app = {
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
// plugin包含install函数
installedPlugins.add(plugin)
// 传入App实例,从而对其进行扩展
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
// plugin就是一个函数
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
)
}
return app
},
})
看use的代码,果然调用install并传入了app,下面再验证一下vuex内部的操作,store.js
class Store {
install (app, injectKey) {
app.provide(injectKey || storeKey, this)
app.config.globalProperties.$store = this
}
}
可见,除了设置全局属性$store,还provide了一个storeKey,这显然是为useStore()
做准备,injectKey.js:
export function useStore (key = null) {
return inject(key !== null ? key : storeKey)
}
造轮子
下面我们小试牛刀,把插件基本结构实现一下。
mini-vue基本结构
<div id="app">
<h3 @click="add">{{counter}}</h3>
</div>
<script src="http://unpkg.com/vue@next"></script>
<script src="http://unpkg.com/vuex@next"></script>
<script>
const { ref, computed, watchEffect } = Vue
const { createStore, useStore } = Vuex
const MiniVue = {
createApp(options) {
return {
mount(selector) {
const parent = document.querySelector(selector)
const ctx = options.setup()
watchEffect(() {
const el = options.render.call(ctx)
parent.innerHTML = ''
parent.appendChild(el)
})
},
}
}
}
const app = MiniVue.createApp({
setup() {
const counter = ref(1)
const add = () {
counter.value++
}
return {
counter, add
}
},
render() {
const h3 = document.createElement('h3')
h3.innerHTML = this.counter.value
h3.addEventListener('click', this.add)
return h3
},
})
app.mount('#app')
</script>
这里我刻意忽略了响应式和vdom,大家可以专注于插件功能实现,
实现use方法
这里简化了use的实现形式,用户只能传入对象类型插件。
const MiniVue = {
createApp(options) {
return {
use(plugin, ...options) {
if(plugin && typeof plugin.install === 'function') {
plugin.install(app, ...options)
} else {
console.warn('插件必须是一个自带install函数的对象');
}
}
}
}
}
实现mini-vuex
下面我们实现一个简版vuex,试试我们插件体系能否生效。我们有几个小目标:
- 声明一个Store类:包含响应式state,可预测修改state,install方法
- 获取Store实例:createStore()
- composition api:useStore()
声明一个Store类
Store类包含响应式state
class Store {
constructor(options) {
this._state = reactive({
data: options.state
})
}
get state() {
return this._state.data
}
}
能够可预测的修改state(commit mutation)
class Store {
constructor(options) {
this._mutations = options.mutations
}
get state() {
return this._state.data
}
commit(type, payload) {
const entry = this._mutations[type]
entry(this.state, payload)
}
}
实现install方法,满足vue插件要求:
const map = {}class Store { install(app, injectKey) { // app.config.globalProperties.$store = this // provide(injectKey || 'store', this) map[injectKey || 'store'] = this }}
提供相关API: createStore()/useStore()
创建Store实例,实现createStore()
function createStore(options) { return new Store(options)}
实现useStore
方法,可以在setup中获取store实例:
function useStore(key = null) { return map[key !== null ? key : 'store']}
应用mini-vuex
创建Store实例
const store = createStore({ state: { counter: 1 }, mutations: { add(state) { state.counter++ } }})
使用store
app.use(store)
使用store中的数据
const app = MiniVue.createApp({ setup() { // const counter = ref(1) // const add = () => { // counter.value++ // } const store = useStore() const counter = computed(() => store.state.counter) const add = () => { store.commit('add') } return { counter, add } },})
完整代码
<div id="app"> <h3 @click="add">{{counter}}</h3></div><script src="http://unpkg.com/vue@next"></script><script src="http://unpkg.com/vuex@next"></script><script> const { reactive, ref, computed, watchEffect, provide, inject } = Vue // const { createStore, useStore } = Vuex const MiniVue = { createApp(options) { return { mount(selector) { const parent = document.querySelector(selector) const ctx = options.setup() watchEffect(() => { const el = options.render.call(ctx) parent.innerHTML = '' parent.appendChild(el) }) }, use(plugin, ...options) { if (plugin && typeof plugin.install === 'function') { plugin.install(app, ...options) } else { console.warn('插件必须是一个自带install函数的对象'); } } } } } const map = {} // Store class Store { constructor(options) { this._state = reactive({ data: options.state }) this._mutations = options.mutations } get state() { return this._state.data } commit(type, payload) { const entry = this._mutations[type] entry(this.state, payload) } install(app, injectKey) { // app.config.globalProperties.$store = this // provide(injectKey || 'store', this) map[injectKey || 'store'] = this } } function useStore(key = null) { return map[key !== null ? key : 'store'] } function createStore(options) { return new Store(options) } const store = createStore({ state: { counter: 1 }, mutations: { add(state) { state.counter++ } } }) const app = MiniVue.createApp({ setup() { // const counter = ref(1) // const add = () => { // counter.value++ // } const store = useStore() const counter = computed(() => store.state.counter) const add = () => { store.commit('add') } return { counter, add } }, render() { const h3 = document.createElement('h3') h3.innerHTML = this.counter.value h3.addEventListener('click', this.add) return h3 }, }) app.use(store) app.mount('#app')</script>