ts能为我们带来什么
以下结论来自官方团队视频教程:
- 增加项目扩展性和维护性,尤其适合开源项目
- vue3对ts的支持比以前更好了
- ts可以增量引入,不需要梭哈
可能的额外负担
当然也有负面影响:
- 额外学习成本
- 影响开发效率
- 排期压力
- 并非万能药丸
- anyscript
整合vue3+ts
下面我们就整合ts到vue3中,主要有以下两种环境:
vue cli环境
新创建项目:
vue create my-project
复制代码
已存在项目:
vue add typescript
复制代码
Vite环境
新创建项目:
npm init @vitejs/app
复制代码
已存在项目,自己手撸~
使用TS编写Vue组件
编写一个组件常见任务:
- 注册组件
data
类型定义props
类型定义methods
和computed
类型支持composition api
中的类型支持
组件定义
使用<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类型定义
data中的对象类型利用类型断言确定数据类型。
类型定义
首先定义一个类型,types.d.ts:
export type Todo = {
id: number;
name: string;
completed: boolean;
}
复制代码
组件定义
定义一个组件Comp.vue,并引入这个类型:
<script lang="ts">
import type { Todo } from "../types";
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类型定义
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要着重标识函数返回类型。
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 = ''
},
},
复制代码
Setup Script
setup script方式编写代码会更加简洁。 下面范例代码目标是不改变template结构,重构script部分,以composition api方式实现,我们来看看有什么变化:
数据定义
单值利用泛型方法ref<T>()
定义
<script setup lang="ts">
import { defineProps, ref, computed } from "vue";
import type { Todo } from "../types"
const items = ref<Todo[]>([]);
items.value.push({
id: 1,
name: "vue3",
completed: false,
});
</script>
复制代码
属性定义
利用defineProps()定义属性,常用手法有两种:
- 泛型方式:
defineProps<{ titleInfo: TitleInfo }>()
- 参数方式:
defineProps({ titleInfo: Object as PropType<TitleInfo> })
计算属性
使用computed()定义,通常类型可以推断出来。
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
vuex整体对ts的支持比较蹩脚,这是以前架构问题引起的,我们一起来感受一下:
创建Store
创建store实例,store/index.ts
import { createStore, Store } from "vuex";
import { State } from "./vuex";
const store = createStore({
state: {
counter: 0,
},
});
export default store;
复制代码
引入vue,main.ts
createApp(App).use(store).mount("#app");
复制代码
使用,Comp.vue
import { mapState } from "vuex";
export default defineComponent({
computed: {
// 映射state counter
...mapState(['counter']),
doubleCounter(): number {
// $store已经有类型了
return this.$store.state.counter * 2;
},
},
}
复制代码
$store类型化
我们希望this.$store
是有明确类型的, 这需要为组件选项添加一个明确类型的$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>;
}
}
复制代码
useStore()类型化
setup
中使用useStore
时要类型化,共需要三步:
- 定义
InjectionKey
- app安装时提供
InjectionKey
- 传递
InjectionKey
给useStore
定义一个InjectionKey
,约束Store
中State
类型,store/index.ts
import { InjectionKey } from "vue";
import { State } from "./vuex";
// define injection key
export const key: InjectionKey<Store<State>> = Symbol();
复制代码
main.ts中作为参数2传入vuex插件
import { key } from "./store";
// 作为参数2传入key
createApp(App).use(store, key).mount("#app");
复制代码
使用时,store就可以有明确类型了,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 = "";
}