Skip to content

Vue3 中使用 Ts

环境搭建

  • 利用官方提供的脚手架
bash
npm init vue@latest

这一指令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具。你将会看到一些诸如 TypeScript 和测试支持之类的可选功能提示:

bash
 Project name: <your-project-name>
 Add TypeScript? No / Yes
 Add JSX Support? No / Yes
 Add Vue Router for Single Page Application development? No / Yes
 Add Pinia for state management? No / Yes
 Add Vitest for Unit testing? No / Yes
 Add Cypress for both Unit and End-to-End testing? No / Yes
 Add ESLint for code quality? No / Yes
 Add Prettier for code formatting? No / Yes

Scaffolding project in ./<your-project-name>...
Done.

如果不确定是否要开启某个功能,你可以直接按下回车键选择 No。在项目被创建后,通过以下步骤安装依赖并启动开发服务器:

bash

cd <your-project-name>
npm install
npm run dev

ref

ref()接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的 property .value

类型定义

ts
function ref<T>(value: T): Ref<UnwrapRef<T>>;
interface Ref<T> {
  value: T;
}

为 ref()标注类型

ref有三种标注类型

  • 通过泛型参数的形式来给ref()增加类型
ts
import { ref } from "vue";

const initCode = ref<string | number>("200");
  • 如果是遇到复杂点的类型,可以自定义 interface 然后泛型参数的形式传入
ts
import { ref } from "vue";

interface User {
  name: string;
  age: string | number;
}

const user = ref<User>({
  name: "前端开发爱好者",
  age: 20,
});
  • 通过使用 Ref 这个类型为 ref 内的值指定一个更复杂的类型
ts
import { ref } from "vue";
import type { Ref } from "vue";

const initCode: Ref<string | number> = ref("200");

比较推荐使用前两种方式,前两种方式其实都是以泛型的形式来标注类型的

reactive

reactive() 接受一个普通对象然后返回该普通对象的响应式代理。

响应式转换是“深层的”:它会递归地将响应式转换应用于嵌套的对象。

类型定义

ts
function reactive<T extends object>(raw: T): UnwrapNestedRefs<T>;

reactive()标注类型

  • 直接给声明的变量添加类型
ts
import { reactive } from "vue";

interface User {
  name: string;
  age: string | number;
}

const user: User = reactive({
  name: "前端开发爱好者",
  age: "20",
});
  • 通过泛型参数的形式来给 reactive()增加类型
ts
import { reactive } from "vue";

interface User {
  name: string;
  age: string | number;
}

const user = reactive<User>({
  name: "前端开发爱好者",
  age: 20,
});

不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。推荐直接给声明的变量添加类型。

computed

computed() 接受一个 getter 函数,返回一个只读的响应式引用。该引用仅在其依赖项发生改变时重新评估。

computed() 的返回值是只读的,你不能修改它的值。因为它是通过 getter 函数生成的,所以你可以像使用普通函数一样使用它。

类型定义

ts
// 只读
function computed<T>(
  getter: () => T,
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>;

// 可写的
function computed<T>(
  options: {
    get: () => T;
    set: (value: T) => void;
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>;

computed()标注类型

  • 从其计算函数的返回值上推导出类型
ts
import { ref, computed } from "vue";

const count = ref<number>(0);

// 推导得到的类型:ComputedRef<string>
const user = computed(() => count.value + "前端开发爱好者");
  • 通过泛型参数的形式来给 computed()增加类型
tsx
import { computed } from "vue";

const user = computed<string>(() => {
  // 若返回值不是 string 类型则会报错
  return "前端开发爱好者";
});

个人推荐使用通过泛型参数的形式

defineProps

为了在声明 props 选项时获得完整的类型推断支持,我们可以使用 defineProps API,它将自动地在 script setup 中使用

为 defineProps() 标注类型

  1. 从它的参数中推导类型:
ts
const props = defineProps({
  name: { type: String, required: true },
  age: Number,
});
  1. 通过泛型参数来定义 props 的类型
ts
const props = defineProps<{
  name: string;
  age?: number;
}>();

当然了,我们也可以吧以上的泛型参数定义成一个单独的 interface

ts
interface Props {
  name: string;
  age?: number;
}

const props = defineProps<Props>();
  1. defineProps 使用默认值
  • types
ts
export interface IData {
  code: string;
  name: string;
}

export interface IChooseAreaProps {
  code: string;
  name: string;
  children?: IChooseAreaProps[];
}
  • 使用默认值
ts
const props = withDefaults(
  defineProps<{
    list: IChooseAreaProps[];
    space: string;
    provinceStyle?: CSSProperties;
    cityStyle?: CSSProperties;
    streetStyle?: CSSProperties;
    provinceplaceholder?: string;
    cityplaceholder?: string;
    streetplaceholder?: string;
    provincedataprops?: IData;
    citydataprops?: IData;
    streetdataprops?: IData;
  }>(),
  {
    list: () => {
      return dataResult;
    },
    space: "20px",
    provinceStyle: () => {
      return {
        width: "150px",
      };
    },
    cityStyle: () => {
      return {
        width: "150px",
      };
    },
    streetStyle: () => {
      return {
        width: "150px",
      };
    },
    provinceplaceholder: "省份",
    cityplaceholder: "市区",
    streetplaceholder: "街道",
    provincedataprops: () => {
      return {
        code: "0",
        name: "天津",
      };
    },
    citydataprops: () => {
      return {
        code: "0",
        name: "天津",
      };
    },
    streetdataprops: () => {
      return {
        code: "0",
        name: "天津",
      };
    },
  }
);
  • 如果是复杂类型的不需要默认值的
ts
import { type Idata } from "@/interfaces/chooseIcon/type";
import { type PropType } from "vue";
const props = defineProps({
  data: {
    type: Object as PropType<Idata>,
    required: true,
  },
});
  • 使用
vue
<template>
  <div>
    <YJ-choose-trend :data="data"></YJ-choose-trend>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { type ChooseTrendProps } from "@/interfaces/chooseTrend/type";
const data = ref<ChooseTrendProps>({
  text: "上升",
  type: "up",
  textColor: "#00c853",
  iconColor: "#3F85FF",
  fontSize: "24px",
});
</script>

<style scoped></style>

个人推荐第二种

defineEmits

为了在声明 emits 选项时获得完整的类型推断支持,我们可以使用 defineEmits API,它将自动地在 script setup 中使用

为 defineEmits 标注类型

defineEmits() 标注类型直接推荐泛型形式

ts
import type { GlobalTheme } from "naive-ui";

const emit = defineEmits<{
  (e: "setThemeColor", val: GlobalTheme): void;
}>();

defineExpose

defineExpose() 编译器宏来显式指定在 script setup 组件中要暴露出去的 property,使得父组件通过模板 ref 的方式获取到当前组件的实例

defineExpose() 标注类型

defineExpose() 类型推导直接使用参数类型自动推到即可

ts
<script setup>
import { ref } from 'vue'

const name = ref<string>('前端开发爱好者')

defineExpose({
  name
})

provide

provide()供给一个值,可以被后代组件注入

类型定义

ts
function provide<T>(key: InjectionKey<T> | string, value: T): void;

为 provide() 标注类型

provide() 标注类型, Vue 提供了一个 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型

ts
import type { InjectionKey } from "vue";

// 建议声明 key (name) 放到公共的文件中
// 这样就可以在 inject 的时候直接导入使用
const name = Symbol() as InjectionKey<string>;

provide(name, "前端开发爱好者"); // 若提供的是非字符串值会导致错误

以上方式是通过定义 key 的类型来标注类型的,还有一种方式直接 key 采用字符串的形式添加

ts
provide("name", "前端开发爱好者");

inject

inject注入一个由祖先组件或整个应用供给的值

类型定义

ts
// 没有默认值
function inject<T>(key: InjectionKey<T> | string): T | undefined;

// 带有默认值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T;

// 使用工厂函数
function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T;

为 inject() 标注类型

provide() 的 key 的类型是声明式提供的话(provide()类型标注的第一种形式)

inject() 可以直接导入声明的 key 来获取父级组件提供的值

ts
// 由外部导入
const name = Symbol() as InjectionKey<string>;

const injectName = inject(name);

如果 provide() 的 key 直接使用的字符串形式添加的, 需要通过泛型参数声明

ts
const injectName = inject<string>("name");

模板 ref

模板 ref 需要通过一个显式指定的泛型参数和一个初始值 null 来创建:

bash

<img ref="el" class="logo" :src="Logo" alt="" />

const el = ref<HTMLImageElement | null>(null)

组件 ref

有时,你可能需要为一个子组件添加一个模板 ref,以便调用它公开的方法

html
<!-- Child.vue -->
<script setup lang="ts">
  const handleLog = () => console.log("前端开发爱好者");

  defineExpose({
    open,
  });
</script>

为了获取 MyModal 的类型,我们首先需要通过 typeof 得到其类型,再使用 TypeScript 内置的 InstanceType 工具类型来获取其实例类型:

ts
<!-- parent.vue -->
<script setup lang="ts">
import Child from './Child.vue'

// 为子组件 ref 声明类型
const child = ref<InstanceType<typeof Child> | null>(null)

// 调用子组件中的方法
const getChildHandleLog = () => {
  child.value?.handleLog()
}
</script>

事件处理器

原生的 DOM 事件标注类型

html
<template>
  <input type="text" @change="handleChange" />
</template>

<script setup lang="ts">
  function handleChange(event: Event) {
    console.log((event.target as HTMLInputElement).value);
  }
</script>