Vue3 + Typescript开发指南
为什么要使用Ts
应不应该使用TS开发Vue3是当前的热门话题,大家主要纠结成本和收益之间的取舍。
什么是TypeScript
官网:构建于JavaScript,增加了静态类型定义
JavaScript变量没有类型,导致代码中各种类型判断语句,冗余度比较高。如果类型没有限制,变量类型可能发生变化,使代码可能出现问题。
ts-01-003.opt.jpg
使用TS的额外成本
image-20210531150655190
是否需要TS
image-20210531151342630
整合vue3+ts
vue cli环境
vue create my-project
image-20210510114727426
Vite环境
npm init @vitejs/app
image-20210531151929571
使用TS编写Vue组件
组件定义
<script lang="ts">
和 defineComponent
<template>
<div>{{ counter }}</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
counter: 0
}
},
});
</script>
工具支持:Volar
data类型定义
利用类型断言确定数据类型
类型定义
types.d.ts
export type Todo = {
id: number;
name: string;
completed: boolean;
}
Comp.vue
import type { Todo } from "../types";
组件定义
<script lang="ts">
export default defineComponent({
data() {
return {
// 利用类型断言
items: [] as Todo[]
}
},
created() {
// 此处会获得类型支持
this.items.push({
id: 1,
name: 'vue3',
completed: false
})
}
});
</script>
模板定义
<template>
<!-- 此处会获得类型支持 -->
<div v-for="item in items" :key="item.id" class="todo-item">
{{ item.name }}
</div>
</template>
props类型定义
对象类型利用类型断言和PropType<T>。
类型定义
export type TitleInfo = {
value: string;
color: string;
}
属性定义
<script lang="ts">
// 属性类型需要PropType支持
import { PropType } from "vue";
import type { TitleInfo } from "../types"
export default defineComponent({
props: {
// 利用泛型类型约束对象类型
titleInfo: Object as PropType<TitleInfo>,
},
})
</script>
模板中使用
<h1 :style="{ backgroundColor: titleInfo?.color }">{{ titleInfo?.value }}</h1>
<Comp :title-info="{ value: '待办事项', color: '#41b883' }"></Comp>
computed计算属性
标识函数返回类型
computed: {
doubleCounter(): number {
return this.counter * 2
}
},
methods方法
标识函数形参和返回类型
methods: {
newTodo(todoName: string): Todo {
return {
id: this.items.length + 1,
name: todoName,
completed: false,
};
},
addTodo(todo: Todo) {
this.items.push(todo);
this.todoName = ''
},
},
data() {
return {
todoName: "",
};
},
<input
type="text"
v-model="todoName"
@keydown.enter="addTodo(newTodo(todoName))"
/>
Setup Script
setup script方式编写代码会更加简洁
数据定义
<script setup lang="ts">
import { defineProps, ref, computed } from "vue";
type Todo = {
id: number;
name: string;
completed: boolean;
};
type TitleInfo = {
value: string;
color: string;
};
const items = ref<Todo[]>([]);
items.value.push({
id: 1,
name: "vue3",
completed: false,
});
</script>
属性定义
defineProps<{
titleInfo: TitleInfo;
}>();
计算属性
const counter = ref(0);
const doubleCounter = computed(() counter.value * 2);
方法
const todoName = ref("");
function newTodo(todoName: string): Todo {
return {
id: items.value.length + 1,
name: todoName,
completed: false,
};
}
function addTodo(todo: Todo) {
items.value.push(todo);
todoName.value = "";
}
使用TS编写Vuex
$store类型化
为ComponentCustomProperties扩展$store属性,store/vuex.d.ts
import { ComponentCustomProperties } from "vue";
import { Store } from "vuex";
// declare your own store states
export interface State {
counter: number;
}
declare module "@vue/runtime-core" {
// provide typings for `this.$store`
interface ComponentCustomProperties {
$store: Store<State>;
}
}
创建Store
store/index.ts
import { createStore, Store } from "vuex";
import { State } from "./vuex";
const store = createStore({
state: {
counter: 0,
},
});
export default store;
引入,main.ts
createApp(App).use(store).mount("#app");
使用,Comp.vue
import { mapState } from "vuex";
export default defineComponent({
props: {
titleInfo: Object as PropType<TitleInfo>,
},
data() {
return {
// counter不需要了
// counter: 0,
};
},
computed: {
// 映射state counter
...mapState(['counter']),
doubleCounter(): number {
// $store已经有类型了
return this.$store.state.counter * 2;
},
},
}
useStore()类型化
setup中使用useStore时要类型化,共需要三步:
- 定义
InjectionKey
- app安装时提供
InjectionKey
- 传递
InjectionKey
给 useStore
store/index.ts
import { InjectionKey } from "vue";
import { State } from "./vuex";
// define injection key
export const key: InjectionKey<Store<State>> = Symbol();
main.ts
import { key } from "./store";
// 作为参数2传入key
createApp(App).use(store, key).mount("#app");
使用,CompSetup.vue
import { useStore } from 'vuex'
import { key } from '../store'
const store = useStore()
const counter = computed(() store.state.counter);
简化使用
封装useStore,避免每次导入key,store/index.ts
import { useStore as baseUseStore } from "vuex";
export function useStore() {
return baseUseStore(key);
}
使用变化,CompSetup.vue
import { useStore } from '../store'
const store = useStore()
模块化
创建模块文件,store/modules/todo.ts
import { Module } from "vuex";
import { State } from "../vuex";
import type { Todo } from "../../types";
const initialState = {
items: [] as Todo[],
};
export type TodoState = typeof initialState;
export default {
namespaced: true,
state: initialState,
mutations: {
initTodo(state, payload: Todo[]) {
state.items = payload;
},
addTodo(state, payload: Todo) {
state.items.push(payload)
}
},
actions: {
initTodo({ commit }) {
setTimeout(() {
commit("initTodo", [
{
id: 1,
name: "vue3",
completed: false,
},
]);
}, 1000);
}
},
} as Module<TodoState, State>;
引入子模块,store/index.ts
import todo from "./modules/todo";
const store = createStore({
modules: {
todo,
},
});
状态中添加模块信息,vuex.d.ts
import type { TodoState } from "./modules/todo";
export interface State {
todo?: TodoState;
}
组件中使用,Comp.vue
export default {
data() {
return {
// items: [] as Todo[],
};
},
computed: {
items(): Todo[] {
return this.$store.state.todo!.items
}
},
methods: {
addTodo(todo: Todo) {
// this.items.push(todo);
this.$store.commit("todo/addTodo", todo);
this.todoName = "";
},
},
}
setup中使用,CompSetup.vue
const items = computed(() store.state.todo!.items)
store.dispatch('todo/initTodo')
function addTodo(todo: Todo) {
// items.value.push(todo);
store.commit('todo/addTodo', todo)
todoName.value = "";
}