TypeScript 入门:给 JavaScript 穿上类型的铠甲
从 JavaScript 到 TypeScript,理解类型系统如何让代码更安全、更易维护
TypeScript 入门:给 JavaScript 穿上类型的铠甲
你是不是也遇到过这种场景?
function greet(person) {
return `Hello, ${person.name}!`
}
greet({ name: 'xiaoming' }) // ✅ Hello, xiaoming!
greet('xiaoming') // ❌ undefined
greet(123) // ❌ undefined
代码跑起来才发现参数传错了,一个低级错误,却浪费了半小时调试。
如果能在写代码时就发现错误,该多好?
TypeScript 就是来解决这个问题的。
一、TypeScript 是什么?
1.1 官方定义
TypeScript is JavaScript with syntax for types.
TypeScript 是带类型语法的 JavaScript。
更直白地说:
TypeScript = JavaScript + 类型系统
1.2 为什么需要类型?
JavaScript 是动态类型语言,变量可以是任何类型:
let value = 'hello' // 字符串
value = 123 // 变成数字
value = true // 变成布尔值
value = {} // 变成对象
这很灵活,但也很危险:
function add(a, b) {
return a + b
}
add(1, 2) // 3 ✅
add('1', '2') // '12' ❌ 预期是 3,实际是字符串拼接
类型系统的作用:
- 提前发现错误
在编译时就发现类型不匹配,而不是运行时才报错。 - 更好的代码提示
编辑器知道变量类型,能提供精准的代码补全。 - 代码即文档
类型本身就是最好的文档,不用猜参数是什么类型。 - 重构更安全
修改一个函数签名,所有调用的地方都会报错提示。
1.3 TypeScript 的编译过程
TypeScript 代码
↓ tsc (TypeScript Compiler)
JavaScript 代码
↓ Node.js / 浏览器
运行
TypeScript 最终会编译成 JavaScript,所以可以运行在任何支持 JavaScript 的环境。
二、安装和配置
2.1 安装 TypeScript
# 全局安装
$ npm install -g typescript
# 项目安装(推荐)
$ npm install --save-dev typescript
# 查看版本
$ tsc --version
2.2 初始化项目
# 创建配置文件
$ tsc --init
会生成 tsconfig.json:
{
"compilerOptions": {
"target": "ES2020", // 编译目标
"module": "ESNext", // 模块系统
"strict": true, // 严格模式
"esModuleInterop": true, // 兼容性
"skipLibCheck": true, // 跳过库文件检查
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
2.3 编译和运行
方式1:手动编译
# 编译单个文件
$ tsc index.ts
# 编译整个项目
$ tsc
# 监听模式
$ tsc --watch
方式2:ts-node(开发环境)
$ npm install --save-dev ts-node
# 直接运行 TypeScript
$ ts-node index.ts
方式3:Vite/Next.js(现代工具链)
现代前端框架都内置 TypeScript 支持,无需手动配置。
三、基础类型
3.1 原始类型
// 字符串
let name: string = 'xiaoming'
// 数字
let age: number = 25
// 布尔值
let isStudent: boolean = true
// null 和 undefined
let n: null = null
let u: undefined = undefined
// Symbol
let sym: symbol = Symbol('key')
// BigInt
let big: bigint = 100n
3.2 数组
// 方式1:类型[]
let numbers: number[] = [1, 2, 3]
let names: string[] = ['xiaoming', 'xiaohong']
// 方式2:Array<类型>
let numbers2: Array<number> = [1, 2, 3]
// 混合类型数组
let mixed: (number | string)[] = [1, 'hello', 2]
3.3 元组(Tuple)
元组是固定长度和类型的数组:
// 定义一个 [string, number] 的元组
let person: [string, number] = ['xiaoming', 25]
// ❌ 错误:类型不匹配
person = [25, 'xiaoming']
// ❌ 错误:长度不匹配
person = ['xiaoming', 25, true]
// 可选元素
let point: [number, number, number?] = [1, 2]
3.4 对象
// 定义对象类型
let user: {
name: string
age: number
email?: string // 可选属性
} = {
name: 'xiaoming',
age: 25
}
// 只读属性
let point: {
readonly x: number
readonly y: number
} = {
x: 10,
y: 20
}
// ❌ 错误:不能修改只读属性
point.x = 30
3.5 any 和 unknown
any:放弃类型检查
let value: any = 'hello'
value = 123
value = true
value.foo.bar // 不会报错,但运行时可能出错
unknown:更安全的 any
let value: unknown = 'hello'
// ❌ 不能直接使用
console.log(value.length)
// ✅ 需要类型守卫
if (typeof value === 'string') {
console.log(value.length) // OK
}
建议:尽量用 unknown 而不是 any。
3.6 void, never
void:没有返回值
function log(message: string): void {
console.log(message)
// 不return 或 return undefined
}
never:永远不会返回
// 抛出异常
function throwError(message: string): never {
throw new Error(message)
}
// 死循环
function infiniteLoop(): never {
while (true) {}
}
四、函数类型
4.1 函数声明
// 完整类型标注
function add(a: number, b: number): number {
return a + b
}
// 箭头函数
const multiply = (a: number, b: number): number => a * b
// 可选参数
function greet(name: string, greeting?: string): string {
return `${greeting || 'Hello'}, ${name}!`
}
// 默认参数
function greet2(name: string, greeting: string = 'Hello'): string {
return `${greeting}, ${name}!`
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0)
}
4.2 函数类型
// 定义函数类型
type MathOperation = (a: number, b: number) => number
const add: MathOperation = (a, b) => a + b
const subtract: MathOperation = (a, b) => a - b
// 函数类型作为参数
function calculate(
a: number,
b: number,
operation: MathOperation
): number {
return operation(a, b)
}
calculate(10, 5, add) // 15
calculate(10, 5, subtract) // 5
4.3 函数重载
// 重载签名
function combine(a: string, b: string): string
function combine(a: number, b: number): number
// 实现签名
function combine(a: string | number, b: string | number): string | number {
if (typeof a === 'string' && typeof b === 'string') {
return a + b
}
if (typeof a === 'number' && typeof b === 'number') {
return a + b
}
throw new Error('Invalid arguments')
}
combine('hello', 'world') // 'helloworld'
combine(1, 2) // 3
// ❌ 错误
combine('hello', 123)
五、接口(Interface)
接口定义对象的结构:
interface User {
name: string
age: number
email?: string // 可选
readonly id: number // 只读
}
const user: User = {
id: 1,
name: 'xiaoming',
age: 25
}
// ❌ 错误:缺少必填属性
const user2: User = {
name: 'xiaohong'
}
// ❌ 错误:不能修改只读属性
user.id = 2
5.1 接口继承
interface Person {
name: string
age: number
}
interface Student extends Person {
studentId: string
grade: number
}
const student: Student = {
name: 'xiaoming',
age: 20,
studentId: '2024001',
grade: 2
}
5.2 索引签名
interface Dictionary {
[key: string]: string
}
const dict: Dictionary = {
hello: '你好',
world: '世界'
}
dict.foo = 'bar' // OK
dict.test = 123 // ❌ 错误:必须是 string
六、类型别名(Type)
type 和 interface 很相似,但更灵活:
// 基本用法
type User = {
name: string
age: number
}
// 联合类型
type Status = 'pending' | 'success' | 'error'
// 交叉类型
type Student = User & {
studentId: string
}
// 函数类型
type MathOp = (a: number, b: number) => number
// 泛型
type Result<T> = {
data: T
error: string | null
}
6.1 Type vs Interface
| 特性 | Type | Interface |
|---|---|---|
| 定义对象 | ✅ | ✅ |
| 继承 | 用 & | 用 extends |
| 联合类型 | ✅ | ❌ |
| 声明合并 | ❌ | ✅ |
建议:
- 定义对象用
interface - 联合类型、元组用
type
七、联合类型和交叉类型
7.1 联合类型(Union)
// 或的关系
type Status = 'loading' | 'success' | 'error'
function showStatus(status: Status) {
console.log(status)
}
showStatus('loading') // ✅
showStatus('success') // ✅
showStatus('pending') // ❌ 错误
// 多种类型
type StringOrNumber = string | number
let value: StringOrNumber = 'hello'
value = 123 // OK
7.2 交叉类型(Intersection)
// 且的关系
type Person = {
name: string
age: number
}
type Employee = {
employeeId: string
department: string
}
type Staff = Person & Employee
const staff: Staff = {
name: 'xiaoming',
age: 25,
employeeId: 'E001',
department: 'IT'
}
八、泛型(Generics)
泛型让类型也可以参数化:
8.1 泛型函数
// 不用泛型:需要为每种类型写一个函数
function identityString(value: string): string {
return value
}
function identityNumber(value: number): number {
return value
}
// 使用泛型:一个函数搞定
function identity<T>(value: T): T {
return value
}
identity<string>('hello') // 'hello'
identity<number>(123) // 123
identity(true) // 类型推断为 boolean
8.2 泛型接口
interface Response<T> {
data: T
code: number
message: string
}
// 使用
const userResponse: Response<User> = {
data: { name: 'xiaoming', age: 25 },
code: 200,
message: 'success'
}
const listResponse: Response<User[]> = {
data: [
{ name: 'xiaoming', age: 25 },
{ name: 'xiaohong', age: 23 }
],
code: 200,
message: 'success'
}
8.3 泛型约束
// 限制泛型必须有 length 属性
function getLength<T extends { length: number }>(value: T): number {
return value.length
}
getLength('hello') // 5
getLength([1, 2, 3]) // 3
getLength({ length: 10 }) // 10
// ❌ 错误
getLength(123)
九、实战案例
9.1 Axios 请求封装
import axios, { AxiosResponse } from 'axios'
// 定义响应结构
interface ApiResponse<T = any> {
code: number
data: T
message: string
}
// 定义用户类型
interface User {
id: number
name: string
email: string
}
// 封装请求函数
async function fetchUser(id: number): Promise<User> {
const response: AxiosResponse<ApiResponse<User>> = await axios.get(
`/api/users/${id}`
)
if (response.data.code !== 200) {
throw new Error(response.data.message)
}
return response.data.data
}
// 使用
const user = await fetchUser(1)
console.log(user.name) // 类型安全,有代码提示
9.2 Vue 3 组件
<script setup lang="ts">
import { ref, computed } from 'vue'
interface Todo {
id: number
text: string
completed: boolean
}
const todos = ref<Todo[]>([])
const unfinishedCount = computed(() => {
return todos.value.filter(t => !t.completed).length
})
function addTodo(text: string) {
todos.value.push({
id: Date.now(),
text,
completed: false
})
}
function toggleTodo(id: number) {
const todo = todos.value.find(t => t.id === id)
if (todo) {
todo.completed = !todo.completed
}
}
</script>
9.3 React 组件
import React, { useState } from 'react'
interface Props {
initialCount?: number
}
const Counter: React.FC<Props> = ({ initialCount = 0 }) => {
const [count, setCount] = useState<number>(initialCount)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
</div>
)
}
十、常见问题
Q1: 什么时候用 any?
尽量不用!
但以下情况可以考虑:
- 三方库没有类型定义
- 动态数据结构(JSON.parse 的结果)
- 复杂到无法定义类型
更好的选择是用 unknown + 类型守卫。
Q2: interface 和 type 如何选择?
建议:
- 定义对象结构用
interface - 联合类型、元组用
type - 团队统一风格即可
Q3: 如何给三方库添加类型?
方式1:安装类型定义包
$ npm install --save-dev @types/lodash
方式2:手动声明
创建 types/lodash.d.ts:
declare module 'lodash' {
export function chunk(arr: any[], size: number): any[][]
}
Q4: 严格模式要开吗?
强烈建议开启!
{
"compilerOptions": {
"strict": true
}
}
虽然初期会有很多错误,但能避免大量运行时 bug。
十一、进阶技巧
11.1 类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function process(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase()) // 类型收窄为 string
}
}
11.2 工具类型
TypeScript 内置了很多工具类型:
interface User {
id: number
name: string
email: string
age: number
}
// Partial: 所有属性变可选
type PartialUser = Partial<User>
// { id?: number; name?: string; ... }
// Required: 所有属性变必填
type RequiredUser = Required<User>
// Pick: 选择部分属性
type UserBasic = Pick<User, 'id' | 'name'>
// { id: number; name: string }
// Omit: 排除部分属性
type UserWithoutEmail = Omit<User, 'email'>
// { id: number; name: string; age: number }
// Readonly: 所有属性变只读
type ReadonlyUser = Readonly<User>
11.3 条件类型
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
// 实用示例:提取 Promise 的返回类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
type A = UnwrapPromise<Promise<string>> // string
type B = UnwrapPromise<number> // number
十二、小明的建议
- 循序渐进
从简单的类型标注开始,不要一上来就写复杂泛型。 - 善用类型推断
TypeScript 很聪明,很多时候不需要手写类型:// ❌ 冗余 const name: string = 'xiaoming' // ✅ 简洁 const name = 'xiaoming' // 推断为 string - 开启严格模式
虽然初期痛苦,但能避免大量 bug。 - 多看开源项目
看 Vue、React 的类型定义,学习最佳实践。 - 工具很重要
用 VS Code,开启 TypeScript 的所有检查。
总结
TypeScript 看起来复杂,但本质很简单:
给 JavaScript 加上类型检查。
好处是:
- ✅ 提前发现错误
- ✅ 更好的代码提示
- ✅ 重构更安全
- ✅ 代码即文档
学习路线:
- 基础类型(number, string, boolean)
- 对象类型(interface, type)
- 函数类型
- 泛型
- 工具类型
掌握这些,日常开发就够用了。
高级技巧(条件类型、infer)可以慢慢学,不着急。
相关文章:
有问题?欢迎评论区留言!
冷笑话时间 🎭
面试官:"为什么要用 TypeScript?"
程序员:"因为 JavaScript 太自由了。"
面试官:"自由不好吗?"
程序员:"自由到可以把字符串当数字加......"
面试官:"......录取。"
(完)