Vue3-10 Vuex

Vue3学习笔记-10 Vuex

For Vue3

安装

1
yarn add vuex@next --save

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {createStore} from 'vuex';

export default createStore({
state: {
count: 0
},
mutations: {
changeCount(state, isAdd) {
state.count = isAdd ? state.count + 1 : state.count - 1;
}
},
actions: {},
modules: {}
});


const app = createApp({ /* 根组件 */ })

// 将 store 实例作为插件安装
app.use(store)

组合式API

通过调用useStore函数,在setup中访问Store,这与在选项是API中使用this.$store是等效的

访问State和Getter

需要创建computed引用并保留响应性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
setup () {
const store = useStore()

return {
// 在 computed 函数中访问 state
count: computed(() => store.state.count),

// 在 computed 函数中访问 getter
double: computed(() => store.getters.double)
}
}
}

访问Mutation和Action

访问Mutation和Action,只需要在setup钩子函数中调用commitdispatch即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useStore } from 'vuex'

export default {
setup () {
const store = useStore()

return {
// 使用 mutation
increment: () => store.commit('increment'),

// 使用 action
asyncIncrement: () => store.dispatch('asyncIncrement')
}
}
}
#

TypeScript支持

看了官方文档,也查了一些资料,目前(2021.05.18),使用Vue3 + Vuex4 + TypeScript的解决方案,想要获得比较完美的TypeScript支持是比较困难的,尤其是在使用了Vuex的modules的情况下,Vuex目前对TypeScript的支持实在太差了

而之前Vue2 + TypeScript + Vuex + Vuex-class的解决方案,虽然书写比较麻烦,但是好歹能够获得比较好的类型支持,但是由于Vuex-class迟迟没有支持Vue3,所以这套解决方案也是不可行的

所以目前使用Vuex4要慎重,可以等等Vuex5,Vuex5带来了很多令人兴奋的特性,比如取消了Mutation,用组合代替了Modules的嵌套等等,或者自己实现一个简单版本的Vuex5也是可行的,具体可以参考这篇文章,介绍的很详细,我很赞同

基于现在的方案,使用Vuex鼓捣出了一个勉强在组合式API中可以用的方案,但是有两个缺点:

  1. Module内部再嵌套Module的话类型判断会报错
  2. commitdispatch都无法获得类型提示

具体的看代码

简易Store

仿照上面的文章,实现了一个简易的Store,好像也能凑合着用,感觉在Vuex5出来之前,也许在业务代码中再完善一下,采用这种方案可能更优雅一点?

两个Store,不存在嵌套关系了,root-store中还是定义全局变量,types定义类型:

1
2
3
4
5
6
7
8
// types.ts
export interface RootStates {
count: number;
}

export interface RootActions {
changeCountAct(payload: {newVal: number}): Promise<void>;
}

index中定义Store的具体实现,借助reactive这个API,返回值是readonly防止被意外更改,Action负责更改State,也可以在里面完成异步请求(不考虑快照和调试的问题了),然后导出一个useRootStore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// root-store/index.ts
import {reactive, readonly} from 'vue';
import {RootStates, RootActions} from './types';
import {mock} from '@/utils';

const createStore = () =>
reactive<RootStates>({
count: 0
});

const createAction = (state: RootStates): RootActions => ({
async changeCountAct({newVal}) {
state.count = await mock(newVal);
}
});

const state = createStore();
const action = createAction(state);

export const useRootStore = () => ({
state: readonly(state),
dispatch: readonly(action)
});

example-store中类似,导出的useExampleStore,这两个Store在外层的index中组装,并且可以相互调用

1
2
3
4
5
6
7
8
9
10
// index.ts
import {useRootStore} from './root-store';
import {useExampleStore} from './exmaple-store';

const allStore = {
root: useRootStore(),
example: useExampleStore()
};

export const useStore = (storeName: keyof typeof allStore = 'root') => allStore[storeName];

在组件中调用root-store时,可以获得类型提示和校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import {useStore} from '@/data';

export default defineComponent({
name: 'Vuex',
setup() {
const store = useStore();

const rootCount = computed(() => store.state.count);

const changeRootCount = (isAdd: boolean) => {
const newVal = isAdd ? store.state.count + 2 : store.state.count - 2;
store.dispatch.changeCountAct({newVal});
};

return {rootCount, changeRootCount};
}
});

同样调用example-store时,为useStore传入example字符串即可,全部代码在这里