文章目录
- 一、TS介绍
-
- 1.什么是TypeScript?
- [2. 为什么必须学TS?](#2. 为什么必须学TS?)
- 3.TS和JS的区别
- 二、TS搭建及运行
-
- 1.TS的运行逻辑
- [2.TS的编译配置文件 tsconfig.json](#2.TS的编译配置文件 tsconfig.json)
- [3.如何将 TypeScript 集成到 Vue 项目中?(实习高频,以 Vue3 + Vite 为例)](#3.如何将 TypeScript 集成到 Vue 项目中?(实习高频,以 Vue3 + Vite 为例))
- 三、TS知识点详解
-
- [1. TS数据类型](#1. TS数据类型)
-
- 1.1.变量声明语法
- 1.2.基本类型:string、number、boolean、symbol、bigint、null、undefined
- [1.3.引用类型:array、 Tuple(元组)、 object(包含Object和{})、function](#1.3.引用类型:array、 Tuple(元组)、 object(包含Object和{})、function)
- 1.4.特殊类型:any、unknow、void、nerver、Enum(枚举)
- [1.5.其他类型:字面量类型、交叉类型(`&`)、联合类型(`|`)、类型别名 (`type`)、类型推论(TS自己判断)、类型断言 (`as`,人为告诉)](#1.5.其他类型:字面量类型、交叉类型(
&)、联合类型(|)、类型别名 (type)、类型推论(TS自己判断)、类型断言 (as,人为告诉))
- [1.3.什么是类型守卫(Type Guard)?](#1.3.什么是类型守卫(Type Guard)?)
- [2. 面向对象](#2. 面向对象)
-
- [2.1. Class(类)](#2.1. Class(类))
- [2.2. 接口interface](#2.2. 接口interface)
- 3.接口与类型别名type的区别
- 3.泛型
-
- [3.1. 为什么要引入泛型?](#3.1. 为什么要引入泛型?)
- [3.2. 泛型的基础使用](#3.2. 泛型的基础使用)
- 3.3.泛型的使用场景
- 四、TS相关面试题
-
- 1.TS是什么?与JS区别
- 2.TS数据类型
-
- 2.1.变量声明
- 2.2.数据类型分四大类
- [2.3. 类型守卫](#2.3. 类型守卫)
- 3.class类
- [4.接口 interface](#4.接口 interface)
- [5.TypeScript 的模块系统(import/export)与 JavaScript 的模块系统有什么区别?](#5.TypeScript 的模块系统(import/export)与 JavaScript 的模块系统有什么区别?)
- [6.TypeScript 中的"命名空间(Namespace)"是什么?它与模块(Module)的区别是什么?](#6.TypeScript 中的“命名空间(Namespace)”是什么?它与模块(Module)的区别是什么?)
- [7.使用 TypeScript 时,你遇到过哪些印象深刻的问题?是如何解决的?(项目)](#7.使用 TypeScript 时,你遇到过哪些印象深刻的问题?是如何解决的?(项目))
- 8.泛型
一、TS介绍
1.什么是TypeScript?
TypeScript (简称:TS),是 JavaScript(JS)的超集 ,可以理解为:TypeScript = JavaScript + 类型系统(类型约束)
2. 为什么必须学TS?
JavaScript 是**「弱类型/动态类型」语言,写代码时不检查变量类型**,这是JS最大的痛点,也是 前端 项目中80%的BUG来源(比如数字和字符串相加、传参类型错误、调用不存在的方法)。
TypeScript 完美解决这个问题,核心优势
- 提前报错,规避BUG :写代码时(编译阶段)就发现类型错误,不用等到运行时才出问题,尤其适合团队开发/大型项目;
- 语法提示,开发提速 :VSCode会根据类型 给出精准的代码提示、属性联想,不用记API,写代码效率提升50%;
- 代码易读,便于维护 :变量/函数的类型一目了然,别人看你的代码能快速理解,自己写的代码隔月也能看懂;
3.TS和JS的区别
- 类型系统 :TS 有静态类型 (编译阶段检查类型 ,变量声明时需指定/推断类型);JS 是动态类型 (运行时才确定类型,易出现类型错误)。
- 编译阶段 :TS 需通过编译器(如 tsc)编译为 JS 才能运行(编译时会做类型校验);JS 可直接在浏览器/Node 环境运行。
- 特性补充 :TS 新增接口(Interface)、泛型(Generic)、枚举(Enum)、类型守卫等特性;JS 无这些原生类型相关特性。
- 开发体验 :TS 支持 工具智能提示、自动补全、类型错误提前预警;JS 开发时需手动判断类型,错误难提前发现。
二、TS搭建及运行
1.TS的运行逻辑
浏览器/Node.js 只 能识别JS代码 ,不能 直接运行TS代码,TS的运行分两步(核心流程,记牢):
- 写代码 :在
.ts后缀的文件中编写TypeScript代码; - 编译转换 :通过
TS编译器(tsc),把.ts 文件编译成浏览器能识别的.js 文件; - 运行代码:执行编译后的 .js 文件即可。
bash
# 1. 全局安装TS编译器(电脑装一次,永久使用,Node.js环境下执行)
npm install -g typescript
# 2. 编译TS文件为JS文件(比如:把index.ts编译成index.js)
tsc index.ts
# 查看版本
tsc -v
# 3. 运行编译后的JS文件
node index.js
2.TS的编译配置文件 tsconfig.json
1. 生成配置文件
bash
# 在项目根目录执行,自动生成tsconfig.json
tsc --init
2. 必改的3个核心配置
打开生成的tsconfig.json,找到以下配置,修改为对应值,其余默认即可:
json
{
"target": "ES6", // 编译后的JS版本为ES6,兼容所有浏览器/Node.js
"module": "ES6", // 模块规范为ES6,支持import/export
"strict": true, // 开启严格模式(重中之重!),强制检查所有类型错误,杜绝隐藏BUG
}
3. 一键编译整个项目
bash
# 执行后,TS会根据tsconfig.json的配置,自动编译项目中所有.ts文件为.js文件
tsc
3.如何将 TypeScript 集成到 Vue 项目中?(实习高频,以 Vue3 + Vite 为例)
1⃣️、初始化 Vue + TS 项目(Vite 方式,最常用):
bash
# 1. 执行创建命令,选择 Vue + TypeScript
npm create vite@latest my-vue-ts-project -- --template vue-ts
# 2. 安装依赖并启动
cd my-vue-ts-project
npm install
npm run dev
2⃣️、核心配置文件
tsconfig.json:TS 编译配置 ,核心字段:compilerOptions.strict: true:开启严格模式(强制类型校验,推荐开启)。compilerOptions.lib: ["ESNext", "DOM"]:指定依赖的库(支持 ES 新特性和 DOM API)。compilerOptions.types: ["vite/client"]:引入 Vite 客户端类型(支持 import.meta.env 等)。
vite.config.ts:Vite 配置文件(用 TS 编写,需导出 defineConfig 对象)。
3⃣️、组件中使用
html
<template>
<div>{{ username }} ({{ age }})</div>
<button @click="updateAge">增加年龄</button>
</template>
<script setup lang="ts">
// 1. 定义 Props 类型(用 defineProps + 泛型)
const props = defineProps<{
username: string; // 必选 Props
age?: number; // 可选 Props
}>();
// 2. 定义响应式变量(TS 自动推断类型)
import { ref } from "vue";
const count = ref(0); // TS 推断 count 为 Ref<number>
// 3. 定义函数(指定参数/返回值类型)
function updateAge(): void {
if (props.age) {
// 若 age 存在,+1(TS 自动推断 props.age 为 number)
console.log(props.age + 1);
}
}
</script>
三、TS知识点详解
1. TS数据类型
1.1.变量声明语法
- 声明语法:
常量名: 数据类型(类型注解的冒号后必须加空格) - 变量的类型一旦确定,就永远不能改变
- TS中声明变量,优先用const,其次用let,完全抛弃var(var无块级作用域,目前已彻底废弃);
- const声明的常量,值不能修改,类型也不能修改,是最安全的声明方式;
- 变量名命名规范:小驼峰 userName ,常量名全大写 MAX_NUM。
javascript
// TS变量声明语法:比JS多了一个 冒号+类型
let 变量名: 数据类型 = 变量值;
const 常量名: 数据类型 = 常量值;
1.2.基本类型:string、number、boolean、symbol、bigint、null、undefined
注意: null 和 undefined 两个类型一旦赋值上,就不能在赋值给任何其他类型
javascript
let str: string = "Domesy" //字符串
let num: number = 7 // 数字
let bool: boolean = true //布尔
let sym: symbol = Symbol(); //symbol
let big: bigint = 10n //bigint
let nu: null = null //null
let un: undefined = undefined //undefined
1.3.引用类型:array、 Tuple(元组)、 object(包含Object和{})、function
- array :声明时写法
数据类型 []或Array<数据类型> - Tuple :是 Array 的一种特殊情况。固定长度、固定类型的数组
- 对象:
- object: 非原始类型。使用
{}定义{属性名:数据类型;...}(直接:object,修改对象属性会出错,因为未设置属性类型) - Object: 所有的原始类型或非原始类型 都可以进行赋值 ,除了
null和undefined
- object: 非原始类型。使用
- function:
- 声明时写法
function和箭头函数。返回值的类型可以写,写:必须要有对应类型的返回值;但通常情况下是省略不写,因为TS的类型推断功能够正确推断出返回值类型 。 - 参数:
- 可选参数:
?配置可有可无的参数。age?: number - 默认参数:
=设定默认值。age: number = 11 - 剩余参数:
...,如...numbers: number[]
- 可选参数:
- 声明时写法
javascript
//array
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]
let arr2: Array<number> = [1, 2, '3'] // error
//要想是数字类型或字符串类型,需要使用 |
let arr3: Array<number | string> = [1, 2, '3'] //ok
//Tuple:固定了元素的类型和个数
let t: [number, string] = [1, '2'] // ok
let t1: [number, string] = [1, 3] // error
let t2: [number, string] = [1] // error
//object(小写)
let obj1: object = { a: 1, b: 2}
obj1.a = 3 // error
let obj2: { a: number, b: number } = {a: 1, b: 2}
obj2.a = 3 // ok
//Object(大写)
let obj: Object;
obj = 1; // ok
obj = {}; // ok
obj = 10n //ok
obj = null; // error
obj = undefined; // error
//function
function setName1(name: string) { //ok
console.log("hello", name);
}
function setName2(name: string):string { //error
console.log("hello", name);
}
function setName4(name: string): string { //ok
console.log("hello", name);
return name
}
//箭头函数,返回值写法同上
const setName5 = (name:string) => console.log("hello", name);
//参数
// 可选参数
const setInfo1 = (name: string, age?: number) => console.log(name, age)
setInfo1('Domesy') //"Domesy", undefined
setInfo1('Domesy', 7) //"Domesy", 7
// 默认参数
const setInfo2 = (name: string, age: number = 11) => console.log(name, age)
setInfo2('Domesy') //"Domesy", 11
setInfo2('Domesy', 7) //"Domesy", 7
// 剩余参数
const allCount = (...numbers: number[]) => console.log(`数字总和为:${numbers.reduce((val, item) => (val += item), 0)}`)
allCount(1, 2, 3) //"数字总和为:6"
1.4.特殊类型:any、unknow、void、nerver、Enum(枚举)
any:任意类型 (关闭类型检查,不推荐滥用),如let random: any = "hello"(后续可赋值为数字)。unknown:未知类型 (比 any 安全,需类型断言后使用),如let value: unknown = 123(需value as number后才能调用数字方法)。void:无返回值类型 (常用于函数返回值),如function log(): void { console.log("log") }。never:永不存在的类型 (如抛出错误的函数、无限循环函数的返回值),如function throwErr(): never { throw new Error("err") }。Enum:可以定义一些带名字的常量 。枚举的类型只能是 string 或 number,定义的名称不能为关键字。数字:不定义属性值,默认从0递增;定义属性值,从值递增
javascript
//any
let d:any; //等价于 let d
d = '1';
d = 2;
//unknow
let u:unknown;
u = true; //ok
u = [1, 2, 3]; //ok
let value1: any = a //ok
let value2: unknown = u //ok
let value3: string = u //error
let value4: number = u //error
u.set() // error
u() // error
//void
const setInfo2 = ():void => { return 2 } // error
const setInfo3 = ():void => { return true } // error
const setInfo4 = ():void => { return } // ok
const setInfo5 = ():void => { return undefined } //ok
//never 当抛出异常的情况和无限死循环
let error = ():never => { // 等价约 let error = () => {}
throw new Error("error");
};
let error1 = ():never => {
while(true){}
}
//enum 不定义属性值,默认从0递增;定义属性值,从值递增
//number类型
enum Status {
up, // 0 若up=8,从8开始递增
dowm, // 1 9
}
//string类型
enum Status {
up = 'up',
dowm = 'dowm'
}
//定义常量
enum Directions {
Up,
Down,
Left,
Right
}
let x = Directions.Up;
1.5.其他类型:字面量类型、交叉类型(&)、联合类型(|)、类型别名 (type)、类型推论(TS自己判断)、类型断言 (as,人为告诉)
- 字面量类型: 限制值为特定字面量,目前支持字符串、数字、布尔三种类型。
let num: 1 | 2 | 3即num值只能是1/2/3 - 交叉类型:
- 定义 :表示变量的类型是"多个类型的合并 ",包含所有类型的属性/方法 ,用
&分隔类型,核心是"且"的关系。 - 语法 :
TypeA & TypeB & TypeC。 - 实习场景 :处理"合并多个类型"的情况(如合并基础用户信息和权限信息)。
- 定义 :表示变量的类型是"多个类型的合并 ",包含所有类型的属性/方法 ,用
- 联合类型(
|)- 定义 :表示变量的类型是"多个类型中的任意一个 ",用
|分隔类型,核心是"或"的关系。 - 语法 :
TypeA | TypeB | TypeC。 - 实习场景 :处理"参数可能有多种类型"的情况(如函数参数支持 string 或 number)。
- 定义 :表示变量的类型是"多个类型中的任意一个 ",用
- 类型别名 (
type): 用 type 给「复杂类型」起一个别名 ,后续直接用别名代替原类型,一次定义,全局复用 。type 类型别名 = 具体类型; - 类型推论: 在TS中如果不设置类型,并且不进行赋值时,将会推论为any类型,如果进行赋值就会默认为类型
- 类型断言 (
as): 告诉TS:这个变量就是XX类型。变量 as 目标类型
javascript
// 类型推论
let a; // 推断为any
let str = '小杜杜'; // 推断为string
let num = 13; // 推断为number
str = true // error Type 'boolean' is not assignable to type 'string'.(2322)
num = 'Domesy' // error
//字面量类型
let str:'小杜杜'
let num: 1 | 2 | 3 = 1
let flag:true
str = '小杜杜' //ok
str = 'Donmesy' // error
num = 2 //ok
num = 7 // error
flag = true // ok
flag = false // error
//交叉类型
type AProps = { a: string }
type BProps = { b: number }
type allProps = AProps & BProps
const Info: allProps = {
a: '小杜杜',
b: 7
}
// 联合类型 |
let value: number | string;
value = 10; // ✅ 正确
value = '10';// ✅ 正确
value = true;// ❌ 错误:只能是数字或字符串
//类型别名 type
type NumOrStr = number | string;
// 后续直接用别名,不用重复写 number | string
let value: NumOrStr = 10;
let value2: NumOrStr = '10';
//类型断言
// ✅ 经典案例:获取DOM元素(前端开发必写)
// TS默认不知道 document.getElementById('box') 是什么类型的DOM,此时用断言告诉TS是HTMLDivElement
const box = document.getElementById('box') as HTMLDivElement;
// ✅ 断言后,就能正常调用div的属性/方法,不报错
box.style.width = '100px';
// ✅ 案例2:联合类型的断言
let value: number | string = 'hello';
// 明确知道value是字符串,断言为string,就能调用字符串的方法
let len = (value as string).length;
参考
原文链接:https://blog.csdn.net/student66666666/article/details/156420409
原文链接:https://blog.csdn.net/2301_79847249/article/details/142494651
1.3.什么是类型守卫(Type Guard)?
类型守卫定义 :在运行时检查变量的类型 ,让 TS 在代码块内部自动推断出变量的具体类型(缩小类型范围,避免类型错误)。
常用类型守卫方式:
- typeof 守卫 (判断基础类型 :
string/number/boolean/symbol)typeof value === "string" - instanceof 守卫 (判断引用类型:数组、类实例 等)
data instanceof Array - in 守卫 (判断对象是否包含某个属性 )
key in obj - is守卫 (通过函数返回 param is Type 语法 自定义)
function isUser(data: unknown): data is User
javascript
//typeof 守卫
type StrOrNum = string | number;
function getLength(value: StrOrNum): number {
if (typeof value === "string") {
return value.length; // TS 推断 value 为 string(可调用 length)
} else {
return value.toString().length; // TS 推断 value 为 number
}
}
//instanceof 守卫
type ArrOrObj = number[] | { value: number };
function getValue(data: ArrOrObj): number {
if (data instanceof Array) {
return data[0]; // TS 推断 data 为 number[]
} else {
return data.value; // TS 推断 data 为 { value: number }
}
}
//in 守卫
interface Dog {
bark: () => void;
}
interface Cat {
meow: () => void;
}
function makeSound(animal: Dog | Cat): void {
if ("bark" in animal) {
animal.bark(); // TS 推断 animal 为 Dog
} else {
animal.meow(); // TS 推断 animal 为 Cat
}
}
//自定义类型守卫(通过函数返回 param is Type 语法自定义)
interface User {
id: string;
name: string;
}
// 自定义守卫:判断是否为 User 类型
function isUser(data: unknown): data is User {
return (
typeof data === "object" &&
data !== null &&
"id" in data &&
"name" in data &&
typeof (data as User).id === "string"
);
}
const apiData: unknown = { id: "123", name: "张三" };
if (isUser(apiData)) {
console.log(apiData.name); // TS 推断 apiData 为 User
}
2. 面向对象
程序中所有操作都需要通过对象来完成。一切皆对象
比如:人这个对象,人姓名、年龄等数据--对象中的属性;人吃饭、睡觉等功能--对象中的方法
2.1. Class(类)
类 = 对象模型,创建对象,即定义类。类=属性+方法
- class类中定义:
- 构造函数constructor:class声明类,constructor声明构造函数。new 生成实例时会调用构造函数。
- 修饰符:
- static :关键词
static开头的属性/方法,称为静态属性/方法,也叫类属性/方法。 - readonly :将属性设置为只读 的。 只读属性必须 在声明时或构造函数里被初始化
- public :任意位置都可访问修改,默认值
- private :私有属性 。只能在类中修改 。需要在类中添加方法让外部访问
- protected :受保护的属性 。只能在类、子类内中访问
- static :关键词
- 存取器getters/setters:截取对对象成员的访问
- extends 继承 :使用
extends继承父类,子类中使用super调用父类的构造函数和方法。 - abstract 抽象 :用
abstract关键字声明的类叫做抽象类 ,声明的方法叫做抽象方法。- 抽象类的作用
- 不能被直接实例化 :只能通过继承由子类实例化。
- 作为基类使用:为多个子类提供公共属性、方法实现或强制实现特定接口。
- 支持部分实现:可包含具体方法(有实现)和抽象方法(无实现,必须由子类实现)。
- 抽象方法 的特点
- 必须声明在抽象类中。
- 只有声明,没有方法体(即无 {} 实现)。
- 子类必须实现所有抽象方法,否则子类也需声明为 abstract。
- 抽象类的作用
1⃣️、class定义
javascript
class Info {
//静态属性
static name1: string = 'Domesy'
//成员属性,实际上是通过public上进行修饰,只是省略了
nmae2:string = 'Hello' //ok
name3:string //error
name4!:string //ok 不设置默认值的时候必须加入 !
//构造方法
constructor(_name:string){
this.name4 = _name
}
//静态方法
static getName = () => {
return '我是静态方法'
}
//成员方法
getName4 = () => {
return `我是成员方法:${this.name4}`
}
//get 方法
get name5(){
return this.name4
}
//set 方法
set name5(name5){
this.name4 = name5
}
}
const setName = new Info('你好')
console.log(Info.name1) // "Domesy"
console.log(Info.getName()) // "我是静态方法"
console.log(setName.getName4()) // "我是成员方法:你好"
2⃣️、继承extends
javascript
class Animal {
type: string;
constructor(_type: string) {
this.type = _type;
}
intro() {
console.log(`I am a ${this.type}`);
}
}
class Dog extends Animal {
constructor(type: string,age: number) {
//属性的继承
super(type);
// 属性的扩展
this.age = age
}
intro() {
//方法的继承
super.intro()
},
// 方法的扩展
sayHi() {
console.log("wang! wang!");
}
}
const dog = new Dog("dog");
dog.intro(); // I am a dog
dog.sayHi(); // wang wang
3⃣️、抽象类 abstract
javascript
abstract class Animal {
abstract makeSound(): void;
sayHi(): void {
console.log("Hi.");
}
}
// error
class Dog extends Animal {
// error 非抽象类"Dog"不会实现继承自"Animal"类的抽象成员"makeSound"
// 必须要对抽象类中的抽象方法进行实现,属性也是一样
}
// good
class Cat extends Animal {
makeSound() :void{
console.log('miao miao~');
}
}
const cat = new Cat();
cat.sayHi(); // hi
cat.makeSound(); // miao miao~
2.2. 接口interface
核心作用 的是"描述对象的结构 "------明确规定 一个对象必须有哪些属性、属性是什么类型。
特性:
- 可选属性 :用
?标记属性 ,表示该属性"可有可无",适合对象属性不固定的场景 - 只读属性 :用
readonly标记属性,表示该属性仅能在对象初始化时赋值 ,后续无法修改,适合ID、创建时间等固定属性。 - 索引签名 :对象的属性名不固定 (比如根据后端返回动态生成的 键值对 ),这时用"索引签名"就能约束动态属性的键和值的类型 ,避免乱加属性。
- 语法:
[key: 键类型]: 值类型,键类型通常是string或number(JS对象键本质是字符串,数字键会自动转字符串)。
- 语法:
接口继承 :接口可以通过extends关键字继承其他接口,实现"契约规则的复用与扩展 "------子接口会包含父接口的所有属性,再新增自己的属性,不用重复定义。
- 支持多继承 ,用
,分隔多个父接口,一次性复用多个契约的规则,如interface User extends HasId, HasName
javascript
// 定义一个Person接口,约定对象必须有name(string)和age(number)属性
interface Person {
name: string;
age: number;
}
// 符合接口契约的对象:类型匹配,可直接赋值
const user: Person = {
name: "张三",
age: 25
};
// 违反契约的错误示例
// const wrongUser: Person = {
// name: "李四" // 报错:缺少age属性
// // age: "26" // 报错:age类型应为number
// };
特性
javascript
// 索引签名:键为string类型,值为string类型
interface DynamicObj {
[key: string]: string;
// 固定属性需兼容索引签名类型(值必须是string)
name: string;
readonly id: number; // 只读属性:初始化后不可改
phone?: string; // 可选属性:可传可不传
}
const obj: DynamicObj = {
id: 123,
name: "张三",
gender: "男", // 动态属性:符合索引签名
address: "北京" // 动态属性:符合索引签名
};
// 错误示例:值类型不符合索引签名
// obj.age = 25; // 报错:age值为number,预期string
// obj = 1002; // 报错:只读属性不能修改
接口继承
javascript
// 父接口:基础用户契约
interface Person {
name: string;
age: number;
}
// 子接口:学生契约,继承Person并新增grade属性
interface Student extends Person {
grade: number; // 学生专属属性
}
// 符合Student契约:需包含Person的所有属性+Student的属性
const student: Student = {
name: "王五",
age: 18,
grade: 12
};
// 错误示例:缺少父接口属性
// const wrongStudent: Student = { name: "赵六", grade: 11 }; // 报错:缺少age属性
3.接口与类型别名type的区别
总结:接口适合定义"对象/函数的结构契约",type适合定义"类型组合(联合、交叉)、基础类型别名"
- 相同点:
- 均可定义对象/函数类型 ,如
interface User { name: string }和type User = { name: string }。 - 均可支持泛型 ,如
interface Box<T> { value: T }和type Box<T> = { value: T }。
- 均可定义对象/函数类型 ,如
- 核心区别:
- 扩展方式:
- Interface :通过
extends扩展,如interface Student extends User { age: number }。 - Type :通过**交叉类型(
&)**扩展,如type Student = User & { age: number }。
- Interface :通过
- 合并能力 :
- Interface :支持"声明合并 "(同名接口会自动合并属性),如
interface User { name: string }和interface User { age: number }合并为{ name: string; age: number }。 - Type :不支持声明合并(同名会报错)。
- Interface :支持"声明合并 "(同名接口会自动合并属性),如
- 适用范围 :
- Interface :仅 能定义对象/函数类型 ,不能定义基础类型 (如
interface Num = number报错)。 - Type :可定义任意类型 (基础类型、联合类型、交叉类型 等),如
type StrOrNum = string | number。
- Interface :仅 能定义对象/函数类型 ,不能定义基础类型 (如
- 使用场景 :
- 优先用 Interface :定义组件 Props、API 返回数据结构等需"可扩展/合并"的对象类型(符合团队协作中类型迭代的需求)。
- 优先用 Type :定义联合类型、交叉类型、基础类型别名 (如 type ID = string | number),或需要复用复杂类型时。
- 扩展方式:
3.泛型
3.1. 为什么要引入泛型?
例如,定义一个 print 函数,这个函数的功能是把传入的参数打印出来,最后再返回这个参数
javascript
// 需求:传入string,返回也是string
function print(arg: string): string{
console.log(arg)
return arg
}
//需求变了:还需要传入number,用联合类型做
function print(arg: string | number): string | number{
console.log(arg)
return arg
}
//需求再变:还需要打印string数组、number数组以及任意类型,使用any
function print(arg: any): any{
console.log(arg)
return arg
}
//这时问题就出来了。传入的是any,返回的也是any(传入的是string,返回的可能是任意类型),就会导致类型不匹配
const res: string = print(123) //是不会报错的
因此,泛型就出现了,来解决输入输出要一致的问题。(当然泛型还解决了其他问题。)
为什么需要泛型(解决两个核心问题):
- 类型安全 :避免使用 any 导致的类型丢失(如用 any 定义数组,无法约束数组元素类型)。
- 代码复用 :一套逻辑支持多种类型(如一个"获取数组第一个元素"的函数,可同时支持 string 数组、number 数组)。
3.2. 泛型的基础使用
我们可以把泛型 比喻为一个类型占位符 ,它告诉编译器:"嘿,这里有一个类型参数,我现在不确定具体是什么类型,但稍后会告诉你。"
泛型定义 :泛型是"类型参数化 "的语法,允许在定义函数、接口、类 时不指定具体类型 ,而是在使用时动态传入类型 (类似函数的参数传递),语法用 <T>(T 为类型占位符 ,可自定义名称)。(泛型参数 <T> 的书写位置必须紧跟在类型名称(函数名、接口名、类名、类型别名)后面)
javascript
//使用泛型解决上面any遗留的问题,用T占位,可以传任意类型,输出也是对应类型
function print<T>(arg:T):T {
console.log(arg)
return arg
}
const res: string = print(123) //此时,输入输出不一致是会报错的
使用时两种方式指定类型:
- 定义要使用的类型
- TS 类型推断,自动推导出类型
javascript
print<string>('hello') // 定义 T 为 string
print('hello') // TS 类型推断,自动推导类型为 string
1⃣️、默认参数: 给泛型加默认参数(使用场景都可加)
javascript
function identity<T = number>(value: T): T {
return value;
}
2⃣️、处理多个函数参数
javascript
//需求:传入一个只有两项的元组,交换元组的第 0 项和第 1 项,返回这个元组
function swap<T, U>(tuple: [T, U]): [U, T]{
return [tuple[1], tuple[0]]
}
3⃣️、keyof :是一个类型操作符 ,用于获取对象类型 的所有属性键的联合类型。它将对象类型的属性键提升到类型层面,使得我们可以进行基于属性名的类型操作。
javascript
interface Person {
name: string;
age: number;
address: string;
}
type PersonKeys = keyof Person; // "name" | "age" | "address"
4⃣️、泛型约束 :需要对泛型参数施加限制 ,使其必须满足某些条件。可以通过 extends 关键字实现泛型约束。
javascript
interface HasLength {
length: number;
}
//T 必须具有 length 属性,否则会报错
function logLength<T extends HasLength>(arg: T) {
console.log(arg.length);
}
项目中使用
javascript
import axios from 'axios'
interface API {
'/book/detail': {
id: number,
},
'/book/comment': {
id: number
comment: string
}
...
}
//T extends keyof API 作为泛型约束,确保类型参数 T 是类型 API 的有效属性名。
function request<T extends keyof API>(url: T, obj: API[T]) {
return axios.post(url, obj)
}
request('/book/comment', {
id: 1,
comment: '非常棒!'
})
3.3.泛型的使用场景
1⃣️、函数中使用泛型
javascript
function identity<T>(value: T): T {
return value;
}
2⃣️、类型别名 type 中使用泛型
javascript
type Print = <T>(arg: T) => T
const printFn: Print = function print(arg){ return arg }
3⃣️、接口 interface 中使用泛型
javascript
interface Iprint<T> {
(arg: T): T
}
//给泛型加默认参数的写法
interface Iprint<T = number> {
(arg: T): T
}
function print<T>(arg:T) {
console.log(arg)
return arg
}
const myPrint: Iprint<number> = print
项目中使用在请求API上
javascript
interface UserInfo {
name: string
age: number
}
function request<T>(url:string): Promise<T> {
return fetch(url).then(res => res.json())
}
request<UserInfo>('user/info').then(res =>{
console.log(res)
})
4⃣️、类 class 中使用泛型
javascript
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
四、TS相关面试题
1.TS是什么?与JS区别
TS :是JS的超集,在js基础上增加了静态类型系统,最终会编译成js运行。
区别
- 类型:ts 是静态类型,编译时就检查类型 ;js 是动态类型,运行时才确定类型
- 编译阶段:ts 需要编译为js 再执行;js可直接执行
- 开发体验:ts 支持工具vscode智能提示、自动补全、类型错误提前预警;js手动判断类型
2.TS数据类型
2.1.变量声明
变量声明常量名: 数据类型
2.2.数据类型分四大类
- 基本类型 :
string、number、boolean、symbol、bigint、null(空类型)、undefined(未定义类型)(继承js) - 引用类型 :
array:写法数据类型 []或Array<数据类型>Tuple(元组):Array 的一种特殊情况。固定长度、固定类型的数组object:使用{}定义{属性名:数据类型;...}(直接:object,修改对象属性会出错,因为未设置属性类型)function:写法function和箭头函数,三种参数:- 可选参数:
?配置可有可无的参数。age?: number - 默认参数:
=设定默认值。age: number = 11 - 剩余参数:
...,如...numbers: number[]
- 可选参数:
- 特殊类型 :(TS独有的 ,独立于基本数据类型 的一组补充类型 )
any:任意类型,关闭类型检测,等同于JS无限制操作。unknow:未知类型(安全的any),只能赋值给null/unknown。用于接收未知来源的值(用户输入、请求返回)void:无返回值类型,只有undefined能赋值给它。用来定义无返回的函数(console)nerver:永不存在的类型,不能操作的类型,不能赋值也不能接收。用来定义(抛出异常函数、无限循环函数)的返回值Enum:枚举,定义带名字的常量,string(键值对形式)/number(默认从0递增,除非设置值)
- 其他类型 :
- 字面量类型 :限制值为特定字面量 ,目前支持字符串、数字、布尔 三种类型。
let num: 1 | 2 | 3- 常用场景 :定义固定选项(如按钮类型、状态码、性别等),配合联合类型一起用。
- 交叉类型(
&) :,用&分隔,变量的类型是"多个类型的合并 "。TypeA & TypeB & TypeC - 联合类型(
|) :用|分隔,变量的类型是"多个类型中的任意一个 "。TypeA | TypeB | TypeC - 类型别名 (
type) :type 给「复杂类型」起一个别名 ,一次定义,全局复用别名代替原类型。type 类型别名 = 具体类型; - 类型推论(TS自己判断) :默认推论为any类型
- 类型断言 (
as,人为告诉) :用as告诉TS变量的类型。变量 as 目标类型
- 字面量类型 :限制值为特定字面量 ,目前支持字符串、数字、布尔 三种类型。
2.3. 类型守卫
类型守卫定义 :在运行时检查变量的类型 ,让 TS 在代码块内部自动推断出变量的具体类型(缩小类型范围,避免类型错误)。
常用类型守卫方式:
- typeof 守卫 (判断基础类型 :
string/number/boolean/symbol)typeof value === "string" - instanceof 守卫 (判断引用类型:数组、类实例 等)
data instanceof Array - in 守卫 (判断对象是否包含某个属性 )
key in obj - 自定义类型守卫 (通过函数返回 param is Type 语法 自定义)
function isUser(data: unknown): data is User
3.class类
- constructor:声明类的构造函数
- extends :继承父类,子类用
super调用父类方法及属性 - abstract :放在类前,叫抽象类;放在方法前,叫抽象方法。
- 抽象类:只声明不能实例化;作为基类提供公共属性方法等;支持部分实现(具体方法(可实现);抽象方法(只声明,子类实现))
- 抽象方法 :只声明(在抽象类中),没有方法体,子类必须实现所有抽象方法。
abstract makeSound(): void
- 修饰符
- static:静态属性/方法。
- readonly :设置为只读 ,必须在声明时或构造函数中初始化
- public :默认值,公开 。任意位置都可访问修改。
- private :私有 。只能在类本身使用。若想访问,在类中添加方法
- protected :受保护的 。只能在类本身及子类中访问
4.接口 interface
作用 :定规则 ,规定对象必须有哪些属性及属性是什么类型
特性:
- 可选属性 :用
?标记属性 ,表示该属性"可有可无 ",适合对象属性不固定的场景 - 只读属性 :用
readonly标记属性 ,表示该属性仅能在对象初始化时赋值 ,后续无法修改 ,适合ID、创建时间 等固定属性。 - 索引签名 :对象的属性名不固定,约束动态属性的键和值的类型 ,避免乱加属性。适用于后端传回的动态数据
- 语法:
[key: 键类型]: 值类型,键类型通常是string或number(JS对象键本质是字符串,数字键会自动转字符串)。
- 语法:
接口继承:
extends继承其他接口,复用及扩展其他规则(子接口会包含父接口的所有属性,再新增自己的属性)- 支持多继承 ,用
,分隔多个父接口,一次性复用多个契约的规则,如interface User extends HasId, HasName
4.1.接口与类型别名type的区别
相同点: 都可定义对象/函数类型 ;都支持泛型
不同点:
- 扩展方式 :
interface通过extends扩展 ;type通过**交叉类型(&)**扩展 - 合并能力 :
interface支持"声明合并 "(同名接口会自动合并属性);type不支持声明合并(同名会报错) - 适用范围 :
interface只能定义对象/函数类型 ;type定义任意类型 (基础类型、联合类型、交叉类型等) - 使用场景 :
interface定义组件 Props、API 返回数据结构等需"可扩展/合并"的对象类型 ;type定义联合类型、交叉类型、基础类型别名 ,或需要复用复杂类型时。
5.TypeScript 的模块系统(import/export)与 JavaScript 的模块系统有什么区别?
- 共同点:
- 均支持 ES 模块 (
import/export)和 CommonJS 模块 (require/module.exports),语法一致。 - 均通过模块隔离代码(避免全局变量污染)。
- 均支持 ES 模块 (
- 不同点:
- 导出/导入内容:
js只能导出/导入**"值"(变量、函数、对象等);ts还可 额外导出/导入"类型"**(接口、类型别名、泛型等),用export type/import type语法; - 模块解析:
ts支持更灵活的模块解析(如 baseUrl、paths 配置,可简化导入路径),需在 tsconfig.json 中配置 - 类型检查:
js仅在运行时 才会发现导入错误 ;ts导入模块时会检查"导入的类型是否存在"
- 导出/导入内容:
6.TypeScript 中的"命名空间(Namespace)"是什么?它与模块(Module)的区别是什么?
命名空间定义 :用 namespace 关键字定义,用于将相关的类型、函数、变量 等封装在一个命名空间内 ,通过 export 暴露内部成 员,避免全局命名冲突,
javascript
// 定义命名空间(可嵌套)
namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
// 未 export 的成员仅在命名空间内可见
function subtract(a: number, b: number): number {
return a - b;
}
}
// 使用命名空间成员(通过 命名空间.成员 访问)
MathUtils.add(1, 2); // 3
区别
| 特性 | 命名空间(Namespace) | 模块(Module) |
|---|---|---|
| 文件关联 | 可在单个文件 中定义,支持跨文件合并 | 每个文件就是一个独立模块 |
| 依赖管理 | 无内置依赖管理 ,需手动通过<reference>引入 |
支持 import/export 管理依赖 |
| 作用域 | 全局作用域内的一个容器(可能污染全局) | 独立作用域(文件内声明默认不暴露) |
| 现代项目适用性 | 适用于早期非模块化 项目(如全局脚本) | 适用于现代模块化项目(推荐使用) |
7.使用 TypeScript 时,你遇到过哪些印象深刻的问题?是如何解决的?(项目)
- API 返回数据类型不匹配
- 问题 :用户列表组件,定义了
User接口(包含id、name、age),但调用 API 后,TS 报错类型"undefined"不能赋值给类型"number "'。排查后发现,API 返回的age字段在部分用户数据 中是undefined(因用户未填写年龄 ),但我定义的 User 接口 中age是必选的 number 类型 ,导致类型不匹配。 - 解决
- 先检查 API 文档,确认
age是可选字段(可能为 undefined); - 修改
User接口,将age改为可选属性 (age?: number); - 在组件中使用
age时,添加空值判断(如user.age || 0),避免渲染错误
- 先检查 API 文档,确认
- 问题 :用户列表组件,定义了
- 第三方 JS 库无 TypeScript 类型
- 问题 :项目需要引入 一个老的日期格式化 JS 库 (·date-format-utils.js·),但该库无官方类型声明,导入后 TS 报错无法找到模块"date-format-utils"的声明文件'。
- 解决 :
- 先尝试安装官方声明文件 (
npm install @types/date-format-utils --save-dev),但发现不存在; - 手动创建声明文件
date-format-utils.d.ts,在文件中声明该模块的类型 (如declare module "date-format-utils" { export function format(date: Date, pattern: string): string; }); - 导入库时,TS 能识别声明的类型,可安全调用 format 方法,且有智能提示。
- 先尝试安装官方声明文件 (
8.泛型
1⃣️、定义: 允许在定义函数、接口、类 式不指定具体类型 ,而是在使用时动态传入类型 (类似函数的参数传递),语法用 <T>(T 为类型占位符 ,可自定义名称)。可以设置默认参数 <T = number>;也可设置多参数 <T,k>
2⃣️、使用时两种方式指定类型:
- 定义要使用的类型
- TS 类型推断,自动推导出类型
3⃣️、keyof :是一个类型操作符 ,用于获取对象类型 的所有属性键的联合类型。
4⃣️、泛型约束 :需要对泛型参数施加限制 ,使其必须满足某些条件。可以通过 extends 关键字实现泛型约束。
5⃣️、泛型使用场景
- 函数中
- 类型别名type中
- 接口interface
- 类class
原文链接:https://blog.csdn.net/Javachichi/article/details/156054946