TypeScript 02:interface、type 与类型收窄
1. interface:给对象形状起名字
第 1 课的 { name: string; age: number } 每次都要重抄一遍------起个名字就能复用:
ts
interface User {
name: string
age: number
}
function greet(u: User) {
return `你好,${u.name}`
}
greet({ name: 'Tom', age: 18 }) // ✅
greet({ name: 'Tom' }) // ❌ Property 'age' is missing in type 'User'
形状可以继承:
ts
interface Vip extends User {
level: number
}
const v: Vip = { name: 'Tom', age: 18, level: 3 }
2. type:另一种起名方式
type 也能给形状起名,还擅长做 interface 做不到的事------给联合、字面量起名:
ts
type ID = string | number
type Status = 'loading' | 'success' | 'error'
let page: Status = 'loading'
page = 'loaded' // ❌ Type '"loaded"' is not assignable to type 'Status'(拼错立刻暴露)
怎么选 :对象形状两者都能用,团队统一一种即可;联合类型、字面量类型只能用 type。
3. 可选属性 ?: 与只读 readonly
ts
interface Profile {
name: string
email?: string // 可有可无,实际类型是 string | undefined
readonly id: number // 只能读,不能改
}
const p: Profile = { name: 'Tom', id: 1 } // 不写 email 合法
p.id = 2 // ❌ Cannot assign to 'id' because it is a read-only property
可选属性的实际类型是 string | undefined,用之前要确认存在:
ts
if (p.email) {
sendTo(p.email) // 这里 TS 知道 email 一定是 string
}
4. 字面量类型与 as const
string 是「任意字符串」,'loading' 本身也是一个类型------值被锁死为这一个:
ts
const ROUTES = {
home: '/',
about: '/about',
} as const // 每个字段锁成字面量类型 '/' 和 '/about',且全部只读
ROUTES.home = '/x' // ❌ Cannot assign to 'home' because it is a read-only property
5. 类型收窄:使用联合类型的前提
联合类型的值,不能直接用某一方的特有能力------必须先收窄:
ts
function describe(id: string | number) {
// ❌ id.toUpperCase():Property 'toUpperCase' does not exist on type 'string | number'
if (typeof id === 'string') {
return id.toUpperCase() // ✅ 这里 TS 知道是 string
}
return id.toFixed(2) // ✅ 这里 TS 知道是 number
}
三种常用收窄手段:
ts
if (typeof x === 'string') {} // ① typeof:区分原始类型
if ('fly' in animal) {} // ② in:对象有没有这个字段
if (r.ok === true) {} // ③ 具体值比较(配合字面量类型)
③ 的经典形态是可辨识联合------后端接口最常见:
ts
type ApiResult = { ok: true; data: string[] } | { ok: false; message: string }
function handle(r: ApiResult) {
if (r.ok) {
return r.data.length // 收窄到成功分支,才有 data
}
return r.message // 收窄到失败分支,才有 message
}
6. unknown:安全的 any
any 是「随便用」;unknown 是「类型未知,但用之前必须收窄」:
ts
const input: unknown = JSON.parse(text)
input.toFixed(2) // ❌ 'input' is of type 'unknown'
if (typeof input === 'number') {
input.toFixed(2) // ✅ 收窄后才能用
}
外部数据(接口返回、JSON)优先按 unknown 处理,逼自己先判断再用。
7. 高频报错速查
| 报错 | 人话 |
|---|---|
| `Property 'x' does not exist on type 'string | number'` |
'input' is of type 'unknown' |
unknown 必须先收窄再使用 |
Cannot assign to 'id' because it is a read-only property |
给 readonly 赋值了 |
Type '"loaded"' is not assignable to type 'Status' |
字面量不在允许列表(多半拼错) |