在 Vue 3 中结合 TypeScript,能够提供更强的类型检查和更好的开发体验。以下是一些最佳实践,可以帮助你有效地在 Vue 3 项目中使用 TypeScript
1. 使用 Vue CLI 或 Vite 创建项目
使用 Vue CLI 或 Vite 创建项目时,可以选择 TypeScript 模板,这样会自动配置 TypeScript 相关的依赖和配置文件。
# 使用 Vue CLI
vue create my-project --preset default
# 在配置中选择 TypeScript
# 使用 Vite
npm init vite@latest my-project --template vue-ts
2. 使用 <script setup> 语法
Vue 3 的 <script setup> 是一个更简洁的语法,可以大大简化组件的定义。使用时,TypeScript 能够更好地推断类型。
<template>
<h1>{{ message }}</h1>
</template>
<script setup lang="ts">
import { ref, reactive, computed} from 'vue';
import type { Ref } from 'vue'
// 泛型 T
const message = ref<string>('Hello, Vue 3 with TypeScript!');
// 通过接口指定类型
const count: Ref<string | number> = ref('1')
count.value = 20
interface Person {
name: string
age?: number
}
// 显式地指定一个 `reactive` 变量的类型 interface or type
const person: Person = reactive({ name: '隔壁老王' })
// 自动推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)
// 通过泛型指定类型
const double = computed<number>(() => {
// 若返回值不是 number 类型则会报错
})
</script>
3. 声明类型
为 props、emit、和 data 声明明确的类型,以获得更好的类型检查。
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
const props = defineProps<{
title: string;
count: number;
}>();
// `基于类型的声明` 可以使我们对所触发事件的类型进行更细粒度的控制
const emit = defineEmits<{
(e: 'increment'): void;
}>();
// 使用 props
console.log(props.title);
</script>
或者
interface Props {
msg?: string
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello,Vue3'
})
// 非 `<script setup>`
import { defineComponent } from 'vue'
export default defineComponent({
props: {
msg: String
},
setup(props) {
props.msg // <-- 类型:string
}
})
// 非 `<script setup>`
import { defineComponent } from 'vue'
export default defineComponent({
emits: ['change'],
setup(props, { emit }) {
emit('change') // <-- 类型检查 / 自动补全
}
})
4. 使用 Vuex 的类型支持
在使用 Vuex 时,可以为 state、getters、mutations 和 actions 定义类型,确保类型安全。
// store.ts
import { createStore } from 'vuex';
interface State {
counter: number;
}
const store = createStore<State>({
state: {
counter: 0,
},
mutations: {
increment(state) {
state.counter++;
},
},
getters: {
getCount(state): number {
return state.counter;
},
},
});
export default store;
5. 使用类型定义的组合函数
自定义组合函数时,明确参数和返回值类型,可以提高代码的可读性和可维护性。
import { ref } from 'vue';
function useCounter(initialValue: number = 0) {
const count = ref<number>(initialValue);
const increment = () => {
count.value++;
};
return { count, increment };
}
6. 使用类型推断
在可能的情况下,充分利用 TypeScript 的类型推断,尽量减少显式类型声明,以提高代码的简洁性。
const message = ref('Hello, World!'); // TypeScript 会推断为 string
7. 使用 Vue Router 的类型支持
在使用 Vue Router 时,确保定义路由的类型,以及对路由参数的限制。
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
8. 定义和使用类型接口
在大型应用中,可能会需要定义多个接口,确保通过接口进行类型的重用。
interface User {
id: number;
name: string;
}
const fetchUser = async (id: number): Promise<User> => {
// API 请求...
};
9.避免 any 类型
尽量避免使用 any 类型,因为它会消除 TypeScript 的类型检查机制。
const message: any = 'Hello'; // 尽量避免
10.函数标注类型
通过明确标注函数参数和返回值的类型,TypeScript 可以在编译时检查类型的一致性。这有助于捕捉潜在的错误,避免在运行时发生意外情况
<template>
<input type="text" @input="handleInput" placeholder="输入一些文字" />
<input type="text" ref="inputElement" placeholder="输入一些文字" />
<div ref="myDiv">这是一个 div 元素</div>
<button @click="increment">点击添加</button>
<p>计数: {{ count }}</p>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
// 为模板引用标注类型
const inputElement = ref<HTMLInputElement | null>(null);
// 声明一个 ref 变量,类型为 HTMLDivElement
const myDiv = ref<HTMLDivElement | null>(null);
// 声明一个响应式变量
const count = ref<number>(0);
// 声明一个响应式变量来存储输入值
const inputValue = ref<string>('');
// 处理输入事件,使用 Event 类型标注 或者默认推导的any(`event` 隐式地标注为 `any` 类型)
// 没有类型标注时,这个 `event` 参数会隐式地标注为 `any` 类型。这也会在 `tsconfig.json` 中配置了 `"strict": true` 或 `"noImplicitAny": true` 时报出一个 TS 错误 建议使用断言
const handleInput = (event: Event): void => {
const target = event.target as HTMLInputElement; // 类型断言
inputValue.value = target.value; // 获取输入框的值
};
// 函数类型标注 无返回值
const increment = (): void => {
count.value++;
};
// 定义一个可选参数的函数 返回string类型
const greet = (name?: string, greeting: string = '你好'): string => {
return `${greeting}, ${name}!`;
};
// 回调函数类型定义
type Callback = (msg: string) => void;
const handleClick = (): void => {
executeCallback((message) => {
console.log(message);
});
};
const executeCallback = (callback: Callback): void => {
callback('按钮被点击了!');
};
// 定义一个泛型函数,去除数组中的重复元素
const uniqueArray = <T>(arr: T[]): T[] => {
return Array.from(new Set(arr));
};
// 示例数组
const numbers = [1, 2, 2, 3, 4, 4, 5];
const strings = ['apple', 'banana', 'apple', 'orange'];
// 使用泛型函数
const uniqueNumbers = ref<number[]>(uniqueArray(numbers));
const uniqueStrings = ref<string[]>(uniqueArray(strings));
// 定义一个泛型函数,用于合并两个对象
const mergeObjects = <T, U>(obj1: T, obj2: U): T & U => {
return { ...obj1, ...obj2 }; // 使用展开运算符合并对象
};
// 示例对象
const objectA = { name: '张三', age: 30 };
const objectB = { hobby: '阅读', location: '北京' };
// 使用泛型函数合并对象 ref中的类型可以单独定义个 type 或者 interface
const mergedObject = ref<{ name: string; age: number; hobby: string; location: string }>(
mergeObjects(objectA, objectB)
);
</script>
11. provide / inject 标注类型
provide 和 inject 通常会在不同的组件中运行。要正确地为注入的值标记类型,Vue 提供了一个 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型
import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'
const key = Symbol() as InjectionKey<string>
provide(key, '123') // 若提供的是非字符串值会导致错误
const test = inject(key) // test 的类型:string | undefined
// 当使用字符串注入 key 时,注入值的类型是 `unknown`,需要通过泛型参数显式声明
const test = inject<string>('key') // 类型:string | undefined
12.组件模板引用标注类型
在 Vue 3 中使用 TypeScript 时,为组件模板引用(即使用 ref 获取组件实例)标注类型是一个好习惯。这将帮助我们在使用组件实例时获得更好的类型检查和代码提示。
<template>
<div>
<p>我是子组件</p>
</div>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
// 定义子组件的 props(可选)
const props = defineProps<{ message: string }>();
</script>
<template>
<div>
<MyChild ref="childRef" message="Hello from parent!" />
<button @click="callChildMethod">调用子组件的方法</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import MyChild from './MyChild.vue';
// 为子组件实例引用标注类型
const childRef = ref<InstanceType<typeof MyChild> | null>(null);
// 定义调用子组件的方法
const callChildMethod = (): void => {
if (childRef.value) {
// 调用子组件的方法,假设子组件有一个名为 showMessage 的方法
// childRef.value.showMessage(); // 如果子组件有此方法,则可以调用
console.log('子组件已被引用');
}
};
</script>
13. 进行类型检查
利用 TypeScript 的静态类型检查,确保编译时捕获错误,提高代码的可靠性。(结合eslint+prettier约束代码规范将得到更好的体验 jsdoc的支持函数的注释调用时候即可得到完整的解释)
14. 参考官方文档
根据项目需要,不断查阅 Vue 3 和 TypeScript 的官方文档,了解最新的功能和最佳实践(多总结多实验多模仿)。