前言
最近看了一下 vuex 的实现,使用的是 watch 来对数据源进行监听。于是我在想可不可以用更简单的方式来实现对数据的双向绑定。
得益于之前写过的# Vue3 TypeScript 全局 Message 提示框。想着可不可以使用 vue component 中 data 天然的数据双向绑定来实现呢?
话不多说,让我们来验证一下。
总流程
具体代码实现
文件目录
index.js
// index.ts
import { render, createVNode } from 'vue';
import Store from './index.vue';
let storeConfig = {};
let store;
const createStore = (opts) => {
const { modules } = opts;
storeConfig = modules;
// 创建一个 div 容器
const container = document.createElement('div');
// 创建 Store 实例,createVNode 的性能比 h 更好
const vm = createVNode(Store, { storeProp: storeConfig });
// 把实例 render 到容器上
render(vm, container);
store = { data: vm.component.data.store };
initConfig(store);
return {
install: (app) => {
app.config.globalProperties.$simpleStore = store;
},
};
};
const getModule = moduleName => store?.data?.[moduleName];
const toyMapState = (moduleName, paramArr) => {
getModule();
const res = {};
paramArr.forEach(async (param) => {
res[param] = () => {
if (store) {
const module = getModule(moduleName);
return module?.state?.[param];
}
};
});
return res;
};
const initConfig = (store) => {
store.commit = (moduleName, funName, data) => {
const module = getModule(moduleName);
module.mutations[funName](module.state, data);
};
// dispatch 同理
};
export { toyMapState, createStore };
index.vue
<template>
<div></div>
</template>
<script>
export default {
props: {
storeProp: {
type: Object,
default: () => ({}),
},
},
data() {
return {
store: {},
};
},
created() {
this.store = this.storeProp;
},
};
</script>
store.js
import { createStore } from './index';
export default createStore({
modules: {
moduleA: {
state: { a: 1234 },
mutations: {
addA(state, num) {
state.a += num;
},
},
},
moduleB: {
state: { b: 2468 },
mutations: {
reduceB(state, num) {
state.b -= num;
},
},
},
},
});
mian.js
// 在 main.js 增加代码
import SimpleStore from './store/install/store';
app.use(SimpleStore);
demo 示例
// demo.vue
<template>
<div>
{{ showData }}
<div>{{ a }} -------------</div>
<div @click="handleClickA">moduleA a +1</div>
<div @click="commitA">moduleA a +10 commit</div>
<div>{{ b }} -------------</div>
<div @click="handleClickB">moduleB b -1</div>
<div @click="commitB">moduleB a -10 commit</div>
<router-view />
</div>
</template>
<script>
import { toyMapState } from './store/install';
export default {
components: {},
watch: {
a(newValue, oldValue) {
console.log(newValue, oldValue, 'a change');
},
},
computed: {
...toyMapState('moduleA', ['a']),
...toyMapState('moduleB', ['b']),
showData() {
return JSON.stringify(this.$simpleStore);
},
},
created() {
console.log(this.$simpleStore, this.a, 'simpleStore');
},
methods: {
handleClickA() {
const moduleA = this.$simpleStore.data.moduleA.state;
moduleA.a += 1;
},
handleClickB() {
const moduleB = this.$simpleStore.data.moduleB.state;
moduleB.b -= 1;
},
commitA() {
this.$simpleStore.commit('moduleA', 'addA', 10);
},
commitB() {
this.$simpleStore.commit('moduleB', 'reduceB', 10);
},
},
};
</script>
总结
通过上述的方式就可以简单的构建一个自己的 store 状态库了。
遇到的问题
- computed 中 toyMapState 的函数是在 main.js 之前执行的。此时 store 还没有初始化。
当时一直想着怎么样让 toyMapState 中的对象延迟到 store 初始化后返回,但是实现不了。后来发现只需要判断一下 store 初始化数据没有再来返回值即可。本质上返回的是一个函数,生命周期函数是在 main.js 执行后才执行。