TypeScript 起步

TypeScript 介绍
知道:TS是带类型语法的JS,是一门强类型的编程语言

TypeScript 好处
知道:TS好处写代码期间就可以发现类型错误
发现:
- 在程序运行的时候 Uncaught TypeError... 这个错误挺常见的
- 这些错误导致在开发项目的时候,需要花挺多的时间去定位和处理 BUG
const num = 18;
num.toLowerCase()
// Uncaught TypeError: num.toLowerCase is not a function
原因:
- JS 是动态类型的编程语言,动态类型最大的特点就是它只能在 代码执行 期间做类型的相关检查(运行),所以往往你发现问题的时候,已经晚了。
方案:
- TS 是静态类型的编程语言,代码会先进行编译然后去执行,在 代码编译 期间做类型的相关检查(写代码),如果有问题编译是不通过的,也就暴露出了问题。
TypeScript 编译
知道:如何使用 tsc 编译 ts 代码

全局安装:
# npm 安装
npm i -g typescript
# yarn 安装
yarn global add typescript
# 部分mac电脑安装需要sudo权限
# sudo npm i -g typescript
# sudo yarn global add typescript
查看版本:
tsc -v
编译 TS:
- 新建 hello.ts 文件
- 当前目录打开命令行窗口,执行 tsc hello.ts 命令,同级目录生成 hello.js 文件
- 执行 node hello.js 验证一下
TypeScript 核心
类型注解
知道:TypeScript 类型注解
语法:let 变量名:类型 = 值
示例代码:
// 约定变量 age 的类型为 number 类型
let age: number = 18;
age = 19;
- : number 就是类型注解,它为变量提供类型约束。
- 约定了什么类型,就只能给该变量赋值什么类型的值,否则报错。
- 而且:约定类型之后,代码的提示也会非常清晰。
错误演示:
let age: number = 18;
// 报错:不能将类型“string”分配给类型“number”
age = '19';
小结:
- 什么是类型注解?
-
- 变量后面约定类型的语法,就是类型注解
- 类型注解作用?
-
- 约定类型,明确提示
原始类型
知道:ts 有哪些类型,掌握:原始类型使用
TS 常用类型:
- JS 已有类型
-
- 简单类型,number、string、boolean、null、undefined
- 复杂类型,对象 数组 函数
- TS 新增类型
-
- 联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any、泛型 等
原始类型:
- 使用简单,完全按照 JS 的类型来书写即可
let age: number = 18;
let myName: string = '小蜗';
let isLoading: boolean = false;
let nullValue: null = null;
let undefinedValue: undefined = undefined;
数组类型
掌握:数组类型的两种写法
- 写法 1
let numbers: number[] = [1, 3, 5];
- 写法 2
let strings: Array<string> = ['a', 'b', 'c'];
推荐使用:
number[]写法
联合类型
掌握:通过联合类型将多个类型合并为一个类型
语法:type1 | type2 ...
需求:数组中有 number 和 string 类型,这个数组的类型如何书写?
let arr: (number | string)[] = [1, 'a', 3, 'b'];
定义:
- 类型与类型之间使用 | 连接,代表类型可以是它们当中的其中一种,这种类型叫:联合类型
练习:给一个定时器 ID 加类型
let timer: number | null = null;
timer = setInterval(() => {}, 1000);
类型别名
掌握:使用类型别名语法给类型取别字
语法:type 别名变量名 = types类型值
示例代码:
// let arr: ( number | string )[] = [ 1, 'a', 4]
// 类型别名: type 类型别名 = 具体类型
type CustomArr = (number | string)[];
let arr: CustomArr = [1, 'a', 4];
类型别名:
- type 类型别名 = 具体类型 基本语法
- 定义类型别名,遵循大驼峰命名规范,类似于变量
- 使用类型别名,与类型注解的写法一样即可
使用场景:
- 当同一类型(复杂)被多次使用时,可以通过类型别名,简化 该类型的使用
type CustomArr = (number | string)[];
let arr: CustomArr = [1, 'a', 4];
let arr2: CustomArr = [2, 'b', 8];
函数类型
基本使用
掌握:给函数指定类型
- 给函数指定类型,其实是给 参数 和 返回值 指定类型。
- 两种写法:
-
- 在函数基础上 分别指定 参数和返回值类型
- 使用类型别名 同时指定 参数和返回值类型
示例代码 1:分别指定
// 函数声明
function add(num1: number, num2: number): number {
return num1 + num2;
}
// 箭头函数
const add = (num1: number, num2: number): number => {
return num1 + num2;
};
示例代码 2:别名指定
type AddFn = (num1: number, num2: number) => number;
const add: AddFn = (num1, num2) => {
return num1 + num2;
};
::: tip 注意:通过类似箭头函数形式的语法来为函数添加类型,只适用于 函数表达式:::
void 类型
掌握:void 函数返回值类型
- 如果函数没有返回值,定义函数类型时返回值类型为 void
const say = (): void => {
console.log('hi');
};
- 如果函数没有返回值,且没有定义函数返回值类型的时候,默认是 void
const say = () => {
console.log('hi');
};
注意:
- 在 JS 中如果没有返回值,默认返回的是 undefined
- 但是 void 和 undefined 在 TypeScript 中并不是一回事
- 如果指定返回值类型是 undefined 那返回值必须是 undefined
const add = (): undefined => {
return undefined;
};
可选参数
掌握: 使用 ? 将参数标记为可选
- 如果函数的参数,可以传也可以不传,这种情况就可以使用 可选参数 语法,参数后加 ? 即可
const fn = (n?: number) => {
// ..
};
fn();
fn(10);
- 练习,模拟 slice 函数,定义函数参数类型
const mySlice = (start?: number, end?: number) => {
console.log('起始Index:', start, '结束Index:', end);
};
mySlice();
mySlice(1);
mySlice(1, 2);
:::tip 注意:
- 必选参数不能位于可选参数后 (start?: number, end: number) 这样是不行的:::
对象类型
基本使用
掌握:对象类型语法
语法:变量名:{key1:type1; key2:type2...}
- TS 的对象类型,其实就是描述对象中的 属性方法 的类型,因为对象是由属性和方法组成的。
// 空对象
let person: {} = {
// 有属性的对象
let person: { name: string } = {
name: '同学',
};
// 有属性和方法,一行书写多个属性 ; 分隔
let person: { name: string; sayHi(): void } = {
name: 'jack',
sayHi() {},
};
// 换行写可以省略 ; 符号
let person: {
name: string;
sayHi(): void;
} = {
name: 'jack',
sayHi() {},
};
小结:
- 使用声明描述对象结构?
{} - 属性怎么写类型?
属性名: 类型 - 方法怎么写类型?
方法名(): 返回值类型
扩展用法
掌握:对象类型中,函数使用箭头函数类型,属性设置可选,使用类型别名。
- 函数使用箭头函数类型
let person: {
name: string
sayHi: () => void
} = {
name: 'jack',
sayHi() {},
};
- 对象属性可选
// 例如:axios({url,method}) 如果是 get 请求 method 可以省略
const axios = (config: { url: string; method?: string }) => {};
- 使用类型别名
// {} 会降低代码可阅读性,建议对象使用类型别名
// const axios = (config: { url: string; method?: string }) => {};
type Config = {
url: string;
method?: string;
};
const axios = (config: Config) => {};
小结:
- 对象的方法使用箭头函数类型怎么写?{sayHi:()=>void}
- 对象的可选属性怎么设置?{name?: string}
- 对象类型会使用 {} 如何提供可阅读性?类型别名
创建一个学生对象,该对象中具有以下属性和方法:
- 属性:必选属性:姓名、性别、成绩,可选属性:身高
- 方法:学习、打游戏(可选)
接口类型
基本使用
掌握:使用 interface 声明对象类型
语法:interface 接口类型名字 {key1:type1; key2?:type2...}
- 接口声明是命名对象类型的另一种方式
// 通过interface定义对象类型
interface Person {
name: string;
age: number;
sayHi: () => void;
}
// 使用类型
let person: Person = {
name: 'jack',
age: 19,
sayHi() {},
};
小结:
- interface 后面是接口名称,和类型别名的意思一样。
- 指定 接口名称 作为变量的类型使用。
- 接口的每一行只能有 一个 属性或方法,每一行不需要加分号。
interface 继承
掌握:使用 extends 实现接口继承,达到类型复用
思考:
- 有两个接口,有相同的属性或者函数,如何提高代码复用?
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
继承:
- 相同的属性或展示可以抽离出来,然后使用 extends 实现继承复用
interface Point2D {
x: number;
y: number;
}
// 继承 Point2D
interface Point3D extends Point2D {
z: number;
}
// 继承后 Point3D 的结构:{ x: number; y: number; z: number }
小结:
- 接口继承的语法:interface 接口A extends 接口B {}
- 继承后 接口A 拥有 接口B 的所有属性和函数的类型声明
type 交叉类型
掌握:使用 交叉类型 实现接口的继承效果
- 实现 Point2D 与 {z: number} 类型合并得到 Ponit3D 类型
// 使用 type 来定义 Point2D 和 Point3D
type Point2D = {
x: number;
y: number;
};
// 使用 交叉类型 来实现接口继承的功能:
// 使用 交叉类型 后,Point3D === { x: number; y: number; z: number }
type Point3D = Point2D & {
z: number;
};
let o: Point3D = {
x: 1,
y: 2,
z: 3,
};
小结:
- 使用 & 可以合并连接的对象类型,也叫:交叉类型
interface vs type
了解:interface 和 type 的相同点和区别
- 类型别名和接口非常相似,在许多情况下,可以在它们之间自由选择。
- 接口的几乎所有特性都以类型的形式可用,关键的区别在于不能重新打开类型以添加新属性,而接口总是可扩展的。
| interface | type |
|---|---|
| 支持:对象类型 | 支持:对象类型,其他类型 |
| 复用:可以继承 | 复用:交叉类型 |
不同的点:
- type 不可重复定义
type Person = {
name: string;
};
// 标识符“Person”重复 Error
type Person = {
age: number;
};
- interface 重复定义会合并
interface Person {
name: string;
}
interface Person {
age: number;
}
// 类型会合并
const p: Person = {
name: 'jack',
age: 18,
};
小结:
- 它们都可以定义对象类型
- 它们都可以复用,interface 使用 extends , type 使用 &
- type 不能重复定义,interface 可以重复会合并
字面量类型
字面量类型介绍
知道:什么是字面量类型?类型是一个具体的值(类似常量)
- js 字面量如:18'jack'['a']{age: 10} 等等。
- 使用 js字面量 作为变量类型,这种类型就是字面量类型。
// : 'jack' 是字面量类型
let name: 'jack' = 'jack';
// : 18 是字面量类型
let age: 18 = 18;
// 报错:不能将类型“19”分配给类型“18”
age = 19;
思考:这两个变量的类型是什么?
let str1 = 'Hello TS';
const str2 = 'Hello TS';
- 通过类型推断发现,str1 类型是 string , str2 类型是 Hello TS
- 原因:str2 是 const 声明的,值只能是 Hello TS,所以类型只能是 Hello TS
字面量类型应用
知道:字面量类型的应用场景
例如:性别只能是 男 和 女,不会出现其他值。
// let gender = '男'
// gender = '女'
// ------------------------
type Gender = '男' | '女'
let gender: Gender = '男'
gender = '女'
小结:
- 字面量类型配合联合类型来使用,表示:一组明确的可选的值
例子:
// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,会有类型提示:
changeDirection('up')
- 解释:参数 direction 的值只能是 up/down/left/right 中的任意一个
- 优势:相比于 string 类型,使用字面量类型更加精确、严谨
类型推断
知道:TS 的的类型推断机制作用?根据定义变量的值,自动添加类型约束
- 在 TS 中存在类型推断机制,在没有指定类型的情况下,TS 也会给变量提供类型。
发生类型推断的几个场景场景:
- 声明变量并初始化时
// 变量 age 的类型被自动推断为:number
let age = 18;
- 决定函数返回值时
// 函数返回值的类型被自动推断为:number
const add = (num1: number, num2: number) => {
return num1 + num2;
};
:::tip 建议:
- 将来在开发项目的时候,能省略类型注解的地方就省略,充分利用TS推断 的能力,提高开发效率。
- 在你还没有熟悉 ts 类型的时候建议都加上类型,比如今天第一次写 ts 最好都写上
- 如果你不知道类型怎么写,可以把鼠标放至变量上,可以通过 Vscode 提示看到类型:::
类型断言
语法:let 变量名 = 值 as 类型
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。 比如,
// aLink 的类型 HTMLElement,该类型只包含所有标签公共的属性或方法
// 这个类型太宽泛,没包含 a 元素特有的属性或方法,如 href
const aLink = document.getElementById('link')
- 但是我们明确知道获取的是一个 A 元素,可以通过 类型断言 给它指定一个更具体的类型。
// 说明:获取html元素类型=》document.querySelector('标签名').__proto__
const aLink = document.getElementById('link') as HTMLAnchorElement
注意:类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,滥用类型断言可能会导致运行时错误
泛型
作用:泛型(Generics)可以在保证类型安全前提下,给别名、接口、函数等添加类型参数,从而实现复用
:::tip
- 软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
- 在TypeScript中,泛型是一种创建可复用代码组件的工具。这种组件不只能被一种类型使用,而是能被多种类型复用。类似于参数的作用,泛型是一种用以增强类型(types)、接口(interfaces)、函数类型等能力的非常可靠的手段。:::
泛型别名
掌握:泛型别名基本使用,实现类型复用
语法:type 类型别名<Type1, Type2...> = {}
// 对后台返回的数据进行类型定义
type User = {
name: string;
age: number;
}
type Goods = {
id: number;
goodsName: string;
}
type Data<T> = {
msg: string;
code: number;
data: T
}
// 使用类型
type UserData = Data<User>
type GoodsData = Data<Goods>
小结:
- 泛型:定义类型别名后加上<类型参数> 就是泛型语法, 使用的时候传入具体的类型即可
- 是一个变量,可以随意命名,建议遵循大驼峰即可。
- 和类型别名配合,在类型别名后加上泛型语法,然后类型别名内就可以使用这个类型参数
- 泛型可以提高类型的复用性和灵活性
泛型接口
掌握:泛型接口基本使用,实现类型复用,了解内置泛型接口
interface 接口名<Type1, Type2...> {}
// 对象,获取单个ID函数,获取所有ID函数,ID的类型肯定是一致的,但是可能是数字可能是字符串
interface IdFn<T> {
id: () => T;
ids: () => T[];
}
const idObj: IdFn<number> = {
id() { return 1 },
ids() { return [1, 2] },
};
- 在接口名称的后面添加 <类型变量>,那么,这个接口就变成了泛型接口,接口中所有成员都可以使用类型变量。
内置的泛型接口:
const arr = [1, 2, 3];
// TS有自动类型推断,其实可以看做:const arr: Array<number> = [1, 2, 3]
arr.push(4);
arr.forEach((item) => console.log(item));
- 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键) 去查看内置的泛型接口
泛型函数
掌握:泛型函数基本使用,保证函数内类型复用,且保证类型安全
语法:const fn = <Type1, Type2...>(a:Type1, b:Type2):Type1=>{}
// 函数的参数是什么类型,返回值就是什么类型
function getId<T>(id: T): T {
return id
}
let id1 = getId<number>(1)
let id2 = getId('2')
// TS会进行类型推断,参数的类型作为泛型的类型 getId<string>('2')
小结
- 泛型函数语法?
-
- 函数名称后加上 , T是类型参数,是个类型变量,命名建议遵循大驼峰即可。
- T 什么时候确定?
-
- 当你调用函数的时候,传入具体的类型,T 或捕获到这个类型,函数任何位置均可使用。
- 泛型函数好处?
-
- 让函数可以支持不同类型(复用),且保证类型是安全的。
- 调用函数,什么时候可以省略泛型?
-
- 传入的数据可以推断出你想要的类型,就可以省略。
// 我需要的类型 { name: string, age?: number } 但是推断出来是 { name: string}
let id2 = getId({name:'jack'})
any 类型
知道:any 类型的作用是逃避 TS 的类型检查
当变量的类型指定为 any 的时候,不会有任何错误,也不会有代码提示,TS会忽略类型检查
let obj: any = { age: 18 }
obj.bar = 100
小结:
- any 的使用越多,程序可能出现的漏洞越多,因此不推荐使用 any 类型,尽量避免使用。
TypeScript 应用
创建 vue-ts 项目
创建一个基于 ts 的 vue 项目,来学习 ts 语法
命令说明❓:npm create vite@latest 项目名 --template vue-ts
# npm 6.x
npm create vite@latest my-vue-ts-app --template vue-ts
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-ts-app -- --template vue-ts
# yarn
yarn create vite my-vue-ts-app --template vue-ts
# pnpm
pnpm create vite my-vue-ts-app --template vue-ts
在基于 vite 的项目中可以直接验证 ts 代码结果,因为已经配置好了 ts 环境。
安装vue-ts插件
开发阶段
- Volar工具对.vue文件进行实时的类型错误反馈(禁用 vetur)
- TypeScript Vue Plugin 工具用于支持在 TS 中 import *.vue 文件
打包阶段vue-tsc工具负责打包时最终的类型检查

① TypeScript与Vue
:::tiptypescript 配合 Vue3 composition-api 使用
staging-cn.vuejs.org/guide/types…
ref与Typescript
掌握:ts中ref函数如何使用
- 通过泛型指定value的值类型,如果是简单值,该类型可以省略
const money = ref<number>(10)
const money = ref(10)
- 如果是复杂类型,推荐显示指定泛型
type Todo = {
id: number
name: string
done: boolean
}
const list = ref<Todo[]>([])
setTimeout(() => {
list.value = [
{ id: 1, name: '吃饭', done: false },
{ id: 2, name: '睡觉', done: true }
]
})
reactive与TypeScript
掌握:ts中reactive函数的使用
reactive() 也会隐式地从它的参数中推导类型:
import { reactive } from 'vue'
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue 3 指引' })
要显式地标注一个 reactive property 的类型,我们可以使用接口:
import { reactive } from 'vue'
interface Book {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue 3 指引' })
computed与Typescript
computed() 会从其计算函数的返回值上推导出类型:
import { ref, computed } from 'vue'
const count = ref(0)
// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')
你还可以通过泛型参数显式指定类型:
const double = computed<number>(() => {
// 若返回值不是 number 类型则会报错
})
事件处理与Typescript
掌握:在ts中如何给事件处理加类型
没加类型:
<script setup lang="ts">
function handleChange(event) {
// `event` implicitly has `any` type
console.log(event.target.value)
}
</script>
<template>
<input type="text" @change="handleChange" />
</template>
处理类型:
说明❓:
- 通过$event查看事件对象类型
- document.querySelector('input').proto查看元素类型
// 1. handleChange($event) 查看$event类型
function handleChange(event: Event) {
// `event` 隐式地标注为 `any` 类型
// 2. document.querySelector('input').__proto__ 查看元素类型
console.log((event.target as HTMLInputElement).value)
}
Template Ref与Typescript
掌握:在ts中通过ref获取dom的操作
模板 ref 需要通过一个显式指定的泛型参数
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const el = ref<HTMLInputElement>()
onMounted(() => {
el.value?.focus()
})
</script>
<template>
<input ref="el" />
</template>
问题说明:当对象的属性可能是 null 或 undefined 的时候,称之为“空值”,尝试访问空值身上的属性或者方法会发生类型错误
- 使用可选链方案

- 逻辑判断方案

- 非空断言

defineProps与Typescript
掌握:ts中defineProps的使用
- defineProps宏函数配合ts的泛型定义props类型校验
// 使用ts的泛型指令props类型
defineProps<{
money: number
car?: string
}>()
- props可以通过withDefaults宏函数指定默认值
<script lang="ts" setup>
const props = withDefaults(defineProps<Props>(), {
money: 1000,
car:'benz'
})
</script>
defineEmits与Typescript
掌握:defineEmits的使用
- defineEmits配合运行时声明
const emit = defineEmits(['change', 'update'])
- defineEmits配合ts 类型声明,可以实现更细粒度的校验
const emit = defineEmits<{
// e 事件名, args 传值
(e: 'changeMoney', money: number): void
(e: 'changeCar', car: string): void
}>()
- 基于类型的声明使我们可以对所触发事件的类型进行更细粒度的控制。
② TypeScript类型声明文件
:::tiptypescript 类型声明文件相关知识:::
基本介绍
知道:TS类型声明文件是什么以及作用
什么是类型什么文件?

在TS中以d.ts为后缀的文件就是类型声明文件,主要作用是为js模块提供类型信息支持,从而获得类型提示
TS 中有两种文件类型:.ts 文件 .d.ts 文件作用是啥?
- .ts 文件:
-
- 既包含类型信息又可执行代码
- 可以被编译为 .js 文件,然后,执行代码
- 用途:编写程序代码的地方
- .d.ts 文件:
-
- 只包含类型信息的类型声明文件
- 不会生成 .js 文件,仅用于提供类型信息
- 用途:为 JS 提供类型信息
内置类型声明文件
知道:什么是内置的类型什么文件
TS为JS运行时可用的所有标准化内置API都提供了声明文件,这些文件既不需要编译生成,也不需要三方提供

说明:
- 这里的lib.es5.d.ts以及lib.dom.d.ts都是内置的类型声明文件,为原生js和浏览器API提供类型提示
- 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容
自定义类型声明文件
共享ts类型
掌握:使用类型声明文件提供需要共享的TS类型
- 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。
- 操作步骤:
-
- 创建 index.d.ts 类型声明文件。
- 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
- 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。
src/types/data.d.ts
export type Person = {
id: number;
name: string;
age: number;
};
App.vue
<script lang="ts" setup>
import { Person } from './types/data'
const p: Person = {
id: 100,
name: 'jack',
age: 19
}
</script>
JS提供类型
了解:使用类型声明文件给JS文件添加类型
- 在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。
- declare 关键字:
-
- 用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
-
- 对于 typeinterface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
- 其他 JS 变量,应该使用 declare 关键字,明确指定此处用于类型声明。
add/index.js
const add = (a, b) => {
return a + b;
};
const ponit = (p) => {
console.log('坐标:', p.x, p.y);
};
export { add, ponit }
add/index.d.ts
declare const add: (a: number, b: number) => number;
type Position = {
x: number;
y: number;
};
declare const ponit: (p: Position) => void;
export { add , ponit};
main.ts
import { add , ponit} from './add';
add(3, 10)
ponit({x: 100, y: 200})
axios封装优化
优化axios数据获取和类型定义
公式:axios.request<any, 自定义返回数据类型>()
- 封装axios
import axios from 'axios'
const request = axios.create({
baseURL: 'yourURL'
})
request.interceptors.request.use((config) => {
return config
})
request.interceptors.response.use((res) => {
return res.data?.data
})
export default request