主题
TypeScript 介绍与TS 特点(Day10 2/2)
本页对应 React + TypeScript + 医通项目,共 6 段视频,约 1 小时 43 分。
学习目标
- 能说明
useDebugValue的作用与使用场景,并在自定义 Hook 中使用它,在 React DevTools 中验证调试值显示变化。 - 能解释
useImperativeHandle与forwardRef的配合方式,编写函数组件并限制父组件通过 ref 可调用的方法和属性。 - 能区分 TypeScript 与 JavaScript 的类型系统差异,说明 TS 在编译阶段进行类型检查和语法提示的优势。
- 能独立搭建 TypeScript 工程化开发环境,完成目录结构、依赖安装、
webpack.config.js与tsconfig.json配置,并通过开发服务器运行验证。 - 能正确定义和使用布尔、数字、字符串、对象、数组、任意、
void、元组、枚举、联合类型等 TS 基本数据类型,并解释各类型的约束效果。 - 能识别 TypeScript 的类型推断结果,说明变量首次赋值类型如何决定后续赋值约束,并在联合类型场景中正确使用类型断言解决属性访问报错。
知识点
TypeScript 是什么:从 JavaScript 的“太灵活”说起
TypeScript 简称 TS,是微软开发的一个开源的编程语言。转写稿里反复强调:TS 完全兼容 JavaScript,是 JavaScript 的超集;所谓“超集”,关键是引入了类型系统,可以理解为“JavaScript 拥有了类型系统之后的形态”。
为什么需要:JavaScript 里变量类型可以随时改变,例如先 let a = 1,后面还能把 a 赋成字符串、布尔值、对象等。语法很灵活,但缺少类型检查,像数字乘字符串这种问题,JS 往往要到运行时才发现;没有类型也少了语法提示。
怎么想:把 TS 看成“带类型标注的 JS”。写代码时就告诉编辑器“这个变量是字符串、这个函数返回数字”,让错误提前暴露。
怎么做:先看最小例子:
ts
let str: string = 'abc';
str = 123; // 报错:不能将类型 number 分配给类型 string易错点:TS 报红并不一定代表最终代码错,转写稿提醒“先别慌”,有时是代码还没写完整;例如函数声明了返回 number,但 return 还没补上时会先报错,写完整后可能就消失。
编译与运行:tsc 与 tsconfig.json
为什么需要:浏览器不认识 .ts 文件,需要先编译成 .js。转写稿中先演示命令编译:全局安装 typescript 后获得 tsc 命令。
怎么做:
bash
npm i -g typescript
tsc 1.ts会生成对应的 .js 文件,例如 let 可能被转成兼容性更好的写法。还可以初始化配置文件:
bash
tsc --init生成 tsconfig.json,其中可配置 target、module、strict 等编译要求。
易错点:转写稿里工程化启动时报过“找不到 tsconfig”的问题,执行 tsc --init 后才正常;另外初始化项目时目录不要有中文,否则 npm init -y 可能报错。
工程化环境:webpack + ts-loader
为什么需要:真实开发不会每写一行就手动 tsc 一次,而是用工程化环境实时编译。
怎么想:入口是 src/index.ts,出口打包到 dist/bundle.js;用 ts-loader 处理 .ts/.tsx;用 html-webpack-plugin 把打包后的 JS 插入 public/index.html;用 webpack-dev-server 起开发服务器。转写稿中端口设为 5000,避免和已有项目冲突。
关键配置思路:
js
module.exports = {
entry: './src/index.ts',
output: { filename: 'bundle.js', path: __dirname + '/dist' },
mode: 'development',
module: {
rules: [{ test: /\.tsx?$/, use: 'ts-loader' }]
},
resolve: { extensions: ['.ts', '.tsx', '.js'] },
devServer: { port: 5000 }
};启动命令:
bash
npx webpack-dev-server --config ./webpack.config.js易错点:转写稿调试时发现配置里误引了 defaultAPP 之类内容,导致启动失败;还遇到缺少 tsconfig.json 报错。排查顺序是:先看入口/插件有没有误引,再补 tsc --init,最后重新启动。
基本类型:boolean、number、string、object、array、any、void
为什么需要:类型系统要能描述常见数据。怎么做:
ts
let b1: boolean = true;
let num1: number = 123;
let str1: string = 'abc';
let obj1: object = { a: 20 };
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
let any1: any = 123;
any1 = 'abc'; // any 不限制
function fn(a: number, b: number): void {
console.log(a + b);
}易错点:object 只能说明是引用类型,数组、函数都能赋值,限制不够精确;any 会让 TS 退化成 JS,转写稿建议“能不用就不用”;void 常用于没有返回值的函数。
元组、枚举、联合类型
为什么需要:数组元素类型可能不固定;代码里用 0/1/2 表示性别语义不清;一个值可能既可能是数字又可能是字符串。
怎么做:
ts
let t: [string, number] = ['a', 1];
enum Gender { Woman, Man, Secret }
let g: Gender = Gender.Man;
let x: number | string = 1;元组本质是特殊数组:长度和每个元素类型固定。枚举让代码语义化,转写稿演示了枚举会生成数字与英文的双向映射。联合类型用 | 表示“或者”。
易错点:联合类型使用属性时,只能用两个类型都有的属性;例如 number | string 直接用 .length 会报错,因为 number 没有 length。
类型断言与类型推断
为什么需要:联合类型下,TS 不知道你当前到底是哪个类型;有些 DOM 查询结果也可能是 null。
怎么做:类型断言是“我担保它是这个类型”:
ts
let x: number | string = 'abc';
if ((x as string).length) {
console.log('是字符串');
}
let oDiv = document.querySelector('#root') as HTMLElement;类型推断则是不写类型,TS 根据首次赋值推断:
ts
let num999 = 123;
num999 = 'abc'; // 报错:不能将 string 分配给 number易错点:断言不是运行时转换,只是告诉 TS“按这个类型检查”;推断则说明首次赋值类型会成为变量类型,后续不能随意改成不兼容类型。
TS 为什么需要类型系统
为什么需要:JavaScript 语法灵活,变量可以先存数字,再存字符串、布尔值甚至对象。灵活带来方便,但也带来问题:很多错误只能在运行时暴露,编辑器也缺少可靠的语法提示。
怎么想:TypeScript 不是另起炉灶,而是给 JavaScript 加上类型系统。可以把它理解为“带类型的 JavaScript”。类型不是运行时逻辑,而是在编写和编译阶段帮你检查。
怎么做:例如声明字符串变量后,再赋数字就会在编辑器中报错:
ts
let str: string = "abc";
str = 123; // 报错:不能将类型 number 分配给类型 string函数也可以约束参数和返回值:
ts
function add(a: number, b: number): number {
return a + b;
}如果返回值写成字符串,TS 会在编译阶段提示“不能将类型 string 分配给类型 number”。对象属性名拼错时,编辑器也会画红线,把“填空题”变成“选择题”。
易错点:TS 报红不等于代码已经运行失败,它是在编写/编译阶段提前提示。看到红线先别慌,先把代码写完整;若写完后仍报错,再检查类型。
TS 为什么完全兼容 JavaScript、还是超集
为什么需要:现有 JavaScript 代码和生态很多,如果 TS 不能兼容 JS,学习和迁移成本会非常高。
怎么想:TS 是 JavaScript 的超集,意味着合法 JavaScript 基本都能在 TS 中使用;TS 在其上增加类型等能力。转写稿里“超级”应纠正为“超集”。
怎么做:新建 .ts 文件,先写普通 JS 代码,例如 let a = 1,它仍然是合法 TS。之后再逐步加类型。
易错点:不要误以为 TS 会替代 JS 运行时。浏览器不认识 .ts,需要编译成 .js 后才能运行。
TS 为什么支持最新 JavaScript 特性
为什么需要:项目里常会用到 ES6 及以上语法,如果工具链不支持,开发会受限。
怎么想:TS 支持较新的 JavaScript 语法特性,并可通过编译配置把代码转换为目标环境可识别的版本。
怎么做:在 tsconfig.json 中通过 target 配置编译目标。转写稿示例中,tsc 编译后会把 let 转成 var,这是为了生成 ES5 语法,提高兼容性。
易错点:target 不是越高越好,要结合运行环境;学习阶段先理解“TS 需要编译成 JS”即可。
如何安装 TypeScript 编译工具
为什么需要:浏览器不能直接运行 .ts 文件,需要 tsc 把 TS 编译成 JS。
怎么想:学习阶段先全局安装 TypeScript 包,获得 tsc 命令;工程化项目则通常安装到项目本地。
怎么做:
shell
npm i -g typescript安装后用下面的命令检查是否成功以及查看版本:
shell
tsc -v易错点:如果提示 tsc 找不到,先确认全局安装是否成功、终端是否重新打开;不同系统还可能涉及全局命令权限问题。
如何用 tsc 编译单个 TS 文件
为什么需要:先跑通“TS → JS”的最小流程,理解编译工具到底做了什么。
怎么想:写一个 .ts 文件,用 tsc 文件名 生成同名 .js 文件,再查看输出。
怎么做:例如新建 1.ts:
ts
let a = 1;执行:
shell
tsc 1.ts会生成 1.js。转写稿中观察到 let 被转成 var,说明编译目标影响了输出语法。
易错点:不要直接在浏览器里引用 .ts;要引用编译后的 .js。每次手写 tsc 适合演示,不适合真实开发中频繁改代码的场景。
如何初始化 tsconfig 与工程化环境
为什么需要:真实开发不会每写一行就手动编译一次,需要配置文件和开发服务器实时编译。
怎么想:用 tsc --init 生成 tsconfig.json,其中可配置 target、module、strict 等;工程化环境则用 webpack、ts-loader、webpack-dev-server 等把 TS 编译并自动刷新。
怎么做:
shell
tsc --init会生成 tsconfig.json。转写稿中提到:target 控制编译后的语法版本,module 控制模块规范,strict 控制严格模式。工程化流程大致是:npm init -y 初始化项目,安装 webpack、webpack-cli、webpack-dev-server、ts-loader、typescript、html-webpack-plugin,创建 webpack.config.js,配置入口、出口、loader、插件和 devServer,再启动开发服务器。
易错点:转写稿调试时出现过“找不到 tsconfig 文件”的报错,补执行 tsc --init 后恢复;项目目录不要用中文,否则 npm init -y 可能出问题;devServer 端口冲突时可换端口。工程化配置先保证能启动、能输出结果,再逐步理解每个配置项。
课堂画面






代码示例
示例 1:基本类型(布尔 / 数字 / 字符串 / object / 数组 / any / void)
ts
// src/ts/1-基本类型.ts
export {} // 关键:让本文件成为独立模块,避免与其他 ts 文件的变量重名冲突
// 1. 布尔 boolean
let b1: boolean = true
// b1 = 123 // ❌ 不能将类型"number"分配给类型"boolean"
// 2. 数字 number:整数、小数、负数、0、NaN 都算 number
let num1: number = 123
num1 = 1.5
num1 = -1
num1 = 0
num1 = NaN
// num1 = '1' // ❌
// 3. 字符串 string
let str1: string = 'abc'
// str1 = 123 // ❌
// 4. object:只限定“引用类型”,对象 / 数组 / 函数都能赋,限定作用很弱
let obj1: object = { a: 20 }
obj1 = [1, 2, 3]
obj1 = function () {}
// 5. 数组:两种写法,都必须同时限定“是数组”和“元素类型”
let arr1: number[] = [1, 2, 3] // 写法一:类型[]
let arr2: Array<number> = [1, 2, 3] // 写法二:泛型方式 Array<类型>
// arr1 = [1, 'abc'] // ❌ 不能将类型"string"分配给类型"number"
let arr3: string[] = ['a', 'b']
let arr4: Array<string> = ['c', 'd']
// 6. any:任意类型,等于关掉类型检查,能不用就不用
let a1: any = 123
a1 = 'abc'
a1 = true
a1 = []
// 7. void:函数没有返回值时使用
function fn(a: number, b: number): void {
console.log(a + b)
}
// function fn2(a: number, b: number): number { } // ❌ 声明了 number 却没 return
function fn2(a: number, b: number): number {
return a + b // 写完整了,报错就消失了
}要点与易错点
export {}:index.ts里 import 了多个 ts 文件后,它们最终会打进同一个 bundle,重名变量会互相冲突;加上这一句每个文件就各自独立作用域。- 类型写在变量名后面,用
:分隔:let str1: string,声明后只能存该类型的值,写错在编译阶段就报错,不需要运行。 object只限定引用类型,不限定具体结构;想精确限定对象属性(只能有 name、age)要用后面章节的接口。- 数组两种写法等价:
number[]与Array<number>,后者是泛型写法,两种都必须限定元素类型。 any一用,TS 就退化成 JS,失去类型系统带来的全部好处,尽量别用。void函数里一写return 数字就会报“不能将类型 string/number 分配给类型 void”;反之声明: number却没写return也会立刻报错——先别慌,可能是代码还没写完。
示例 2:元组 / 枚举 / 联合类型 / 类型断言 / 类型推断
ts
// src/ts/2-其他类型.ts
export {}
// 1. 元组:长度固定、每个元素类型也固定的数组
let t1: [number, string] = [1, 'abc']
// let t2: [number, string] = ['abc', 1] // ❌ 顺序不对
// let t3: [number, string] = [1, 'abc', 2] // ❌ 长度固定,第三个不认识
console.log(t1[0].toFixed(2)) // 编辑器能提示 number 的方法
console.log(t1[1].length) // 编辑器能提示 string 的方法
// 2. 枚举:让代码语义化
enum Gender {
Woman = 100, // 手动指定后,后面的从 100 起依次 +1
Man,
Secret
}
console.log(Gender) // 双向映射:{100:'Woman',101:'Man',102:'Secret',Woman:100,...}
console.log(Gender.Woman) // 100
console.log(Gender[100]) // 'Woman'
let sex: number = Gender.Woman
if (sex === Gender.Woman) {
console.log('性别是女') // 比 if (sex === 1) 好读得多
}
// 3. 联合类型:用 | 表示“或”
let x: number | string = 1
x = 'abc'
// x = true // ❌ 不在联合范围内
// 联合类型的坑:只能用两个类型“共有”的属性
function getX(x: number | string) {
// console.log(x.length) // ❌ number 上没有 length
console.log(x.toString()) // ✅ number 和 string 都有 toString
if ((x as string).length) { // 类型断言:我担保它是 string
console.log('是字符串')
} else {
console.log('是数字')
}
}
getX('abc')
getX(123)
// 4. 类型断言:querySelector 可能拿到 null,断言后可直接用元素属性
let oDiv = document.querySelector('#root') as HTMLElement
oDiv.style.color = 'red'
// 5. 类型推断:不写类型,首次赋值的类型就是变量类型
let num999 = 123
// num999 = 'abc' // ❌ 已推断为 number,不用手写也报错要点与易错点
- 元组本质还是数组,只是“长度 + 每个位置类型”都锁死;多一个元素、顺序写反都会报错。
- 枚举是双向映射对象,
Gender.Woman得数字、Gender[100]得名字;给某个成员赋了值,后面的成员会以它为基准+1。 - 联合类型
number | string使用时只能访问两者共有的属性(如toString),访问length会报错,因为number没有。 - 报错时用类型断言
x as string(或as HTMLElement)“担保”类型,编译即可通过——这是断言,不是转换,别乱用。 - 类型推断:
let num999 = 123自动推断为number,再赋字符串直接报错,这就是“从填空题变成选择题”。
示例 3:环境搭建与编译(hello.ts + tsc)
ts
// hello.ts
let str: string = 'abc'
// str = 123 // ❌ 编译阶段就报错:不能将类型"number"分配给类型"string"
function add(a: number, b: number): number {
return a + b
// return 'abc' // ❌ 不能将类型"string"分配给类型"number"
}
console.log(add(1, 2))shell
# 1. 全局安装 TS 编译工具
npm i -g typescript
# 2. 检查是否安装成功及版本
tsc -v
# 3. 把 ts 编译成浏览器能识别的 js(会生成 hello.js,let 被转成 var,ES5 语法)
tsc hello.ts
# 4. 把当前目录初始化成 TS 项目目录,生成 tsconfig.json
tsc --init要点与易错点
- 浏览器只认
.js,.ts文件必须编译;tsc 文件名是最基础的单个文件编译方式,只用于了解原理。 tsconfig.json里常见配置:target(编译成哪个 JS 语法版本)、module(如 commonjs)、strict(严格模式,改 false 即非严格)。- 真实开发不会写一行就
tsc一次,而是用工程化环境(webpack +ts-loader)实时编译;且初始化目录时路径不能有中文,否则npm init -y会报错。 - 编译报错的红波浪线不一定代表代码有问题,可能是代码还没写完,写完再看;写完后仍报错才检查类型。
常见错误
- 现象:执行
tsc -v提示“命令不存在”。原因:未全局安装 TypeScript,或安装后终端 PATH 未刷新。修法:执行npm i -g typescript,重开终端;仍失败时检查 npm 全局 bin 目录是否在 PATH 中。 - 现象:浏览器直接打开/引入
.ts文件报错。原因:浏览器不识别 TypeScript。修法:用tsc编译为.js,或按工程化方式配置webpack + ts-loader + webpack-dev-server。 - 现象:多个
.ts文件里变量名相同就报“无法重新声明变量”。原因:文件未形成独立模块,变量落在同一作用域。修法:在每个独立练习文件末尾加export {}。 - 现象:联合类型变量访问
.length等属性时报错。原因:联合类型只能安全访问各类型共有属性。修法:先用typeof收窄类型;确认类型时再用as断言,不要滥用。 - 现象:给函数组件直接写
ref报错或拿不到实例。原因:函数组件本身不能直接绑定ref。修法:用forwardRef包裹,并用useImperativeHandle只暴露允许父组件调用的方法。 - 现象:
webpack-dev-server启动失败,提示找不到配置/入口/模块。原因:目录含中文、入口路径错误、tsconfig.json缺失或webpack.config.js被误改。修法:使用英文目录,核对entry/output/loader/resolve,补建tsconfig.json,再执行启动脚本。 - 现象:大量使用
any后类型检查形同虚设。原因:any关闭了该值的类型约束。修法:优先写明确类型;不确定时用unknown并收窄;不要用any图省事。
练习
- 搭建一个最小 TS 工程:包含
src/index.ts、public/index.html、webpack.config.js、package.json、tsconfig.json,配置npm start后能在浏览器控制台输出一段文本。 - 在同一文件中定义布尔、数字、字符串、数字数组、字符串数组、元组、枚举、联合类型变量;故意给每个类型赋一次错误值,记录报错并修正。
- 编写
getLength(x: number | string): number,分别处理数字和字符串,要求不使用as断言,只能用类型收窄。 - 实现父子组件 ref 练习:父组件只能调用子组件暴露的
changeColor(),不能直接操作子组件 DOM;使用forwardRef + useImperativeHandle完成。 - 写一个自定义 Hook 模拟在线/离线状态,并用
useDebugValue在 React DevTools 中显示当前状态;对比注释前后调试面板差异。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | useDebugValue | 05:10 |
| 2 | useImpretiveHandle | 25:07 |
| 3 | typescript基本介绍及使用 | 14:33 |
| 4 | typescript-工程化环境构建 | 17:33 |
| 5 | ts基本数据类型 | 39:24 |
| 6 | 类型推断 | 01:40 |
小结
- TypeScript 的核心价值是编译期类型检查与提示,不是把 JavaScript 换掉。
- 学习阶段可用
tsc理解编译过程,真实项目应使用工程化环境实时编译。 tsconfig.json决定编译目标、模块规范和严格模式,工程搭建时不能漏。- 基础类型、数组、元组、枚举、联合类型要配合“赋值错误”反复练,才能形成类型直觉。
- 联合类型优先类型收窄,
as断言只用于你确定类型且无法收窄的场景。 any会削弱 TS 的意义,能不用就不用;不确定类型时优先考虑unknown。- 函数组件绑定 ref 必须配合
forwardRef,对外能力用useImperativeHandle做最小暴露。 - 调试型 Hook 可借助
useDebugValue提升 DevTools 可读性,但它不改变业务逻辑。