九個(gè)技巧讓你從TypeScript新手晉級(jí)為大佬 ??
Typescript 是你在 2025 年掌握 web 开发(无论是 前端、后端 还是 全栈)的必备工具。它是避免 JavaScript 常见陷阱的绝佳工具,但对于初学者来说可能会觉得有些难以入手。这里有 9 个技巧,能够帮助你从新手迅速成长为专业的 Typescript 开发者!
不像大多数初学者所想,你不需要为所有东西都显式地定义类型。如果你提供一些线索,TypeScript 足够聪明来推断类型。
// 基本情况
const a = false; // 自动推断为布尔值
const b = true; // 自动推断为布尔值
const c = a || b; // 自动推断为布尔值
// 特殊情况下的枚举
enum 计数器操作类型 {
Increment = "INCREMENT",
IncrementBy = "INCREMENT_BY",
}
interface IncrementAction {
type: 计数器操作类型.Increment;
}
interface IncrementByAction {
type: 计数器操作类型.IncrementBy;
payload: number;
}
type CounterAction = IncrementAction | IncrementByAction;
function reducer(state: number, action: CounterAction) {
switch (action.type) {
case 计数器操作类型.Increment:
// TS 推断此时的 action 是 IncrementAction 类型
// 且没有负载
return state + 1;
case 计数器操作类型.IncrementBy:
// TS 推断此时的 action 是 IncrementByAction 类型
// 且带有 number 类型的负载
return state + action.payload;
default:
// 返回 state
return state;
}
}
全屏,退出全屏
2. 字面类型(Literal Types)当你需要一个变量来保存特定值时,字面值类型
就很有用了。假设你正在构建一个本地库,用来告知用户关于权限状态信息,你可以这样做:
type PermissionStatus = "granted" | "denied" | "undetermined";
const permissionStatus: PermissionStatus = "granted"; // 正确
const permissionStatus: PermissionStatus = "random"; // 错误
点击全屏按钮可以进入全屏模式,再次点击则退出。
字面量
不仅局限于字符串,也适用于整数和布尔类型。
interface 未认证用户 {
身份验证状态: false;
}
interface 已认证用户 {
data: {
/* ... */
};
身份验证状态: true;
}
type 用户 = 未认证用户 | 已认证用户;
const 用户: 用户 = {
身份验证状态: false,
}; // 正确
全屏 退出全屏
注:为了使上面的例子更真实,我们使用了 Union
与 字面类型
一起使用。你也可以将它们单独作为类型使用,但这会使它们显得有些多余(类似于 类型别名
的作用)
type UnauthenticatedUserData = null; // 未认证用户数据类型为null
全屏, 退出全屏
3. 枚举类型:如 TypeScript 文档中定义为:
枚举是TypeScript中少数几个特性之一,而不是JavaScript类型扩展的特性。
枚举允许开发人员定义一组命名常量。使用枚举可以更方便地表达意图或定义一组不同的情况。TypeScript支持数字枚举和字符串枚举。
你可以按照以下方式定义枚举(enums
):
枚举类型 PrivacyStatus {
公开的,
私有的,
}
点击进入全屏,点击退出全屏
另外,你还可以定义 字符串类型 或 数字类型 枚举类型
// 公开和私密状态
类型 PrivacyStatus {
Public = '公开',
Private = '私密',
}
点击全屏进入/退出全屏
默认情况下(如果没有特别指定),枚举值默认从0开始。例如,enum
的默认值为:
类型 PrivacyStatus {
公开, // 0
私密, // 1
}
进入全屏模式 退出全屏模式
但是如果引入一个新的枚举值,现有 enums
的值也会跟着改变。
enum 隐私状态枚举 {
公开, // 0 - 公开
仅限于, // 1 - 仅限于
除了排除的, // 2 - 除了排除的
私密, // 3 - 私密
}
点击全屏 查看完毕退出全屏
如果你打算在数据库中存储枚举值,最好是为每个enum
指定它对应的值,而不是使用默认值。否则,一旦enum
中的值发生变化,可能会遇到各种麻烦。
类型守卫 是一种用来确定变量具体类型的工具。它们是用于在运行时检查值类型的函数,帮助你确认该变量的具体类型。
// 继续之前的例子
interface UnauthenticatedUser {
isAuthenticated: false;
}
interface AuthenticatedUser {
data: {
/* ... */
};
isAuthenticated: true;
}
const isAuthenticatedUser = (user: User): user 是 AuthenticatedUser =>
user.isAuthenticated;
if (isAuthenticatedUser(user)) {
// 用户是已认证用户,可以访问 data 属性
console.log(user.data);
}
全屏;退出全屏
你可以根据需要定义自定义的 类型守卫,但如果你和我一样偷懒,可以试试 @rnw-community/shared,它提供了一些常用的类型守卫,例如 isDefined
、isEmptyString
、isEmptyArray
等。
当你有一个对象含有动态键时,你可以用索引类型或Record
来定义它的类型:
枚举 ParticipationStatus {
// 加入
Joined = "JOINED",
// 离开
Left = "LEFT",
// 待定
Pending = "PENDING",
}
// 这里使用索引签名
interface ParticipantData {
[id: string]: ParticipationStatus;
}
// 使用 Record 类型
type ParticipantData = Record<string, ParticipationStatus>;
// 常量 participants 定义
const participants: ParticipantData = {
id1: ParticipationStatus.Joined,
id2: ParticipationStatus.Left,
id3: ParticipationStatus.Pending,
// ...
};
全屏模式 退出全屏
注:在上面的代码中,你应该使用 索引签名(index signature) 或 Record
,而不是同时使用两者 — 这将导致错误,提示说 重复的标识符 'ParticipantData'
偶尔,你可能希望创建一个 方法 或 类,它们可以处理多种类型的数据。泛型 让你能够处理多种类型的数据。你可以用尖括号 <>
定义一个 泛型类型:
const getJsonString = <T>(data: T) => JSON.stringify(data, null, 2);
// 示例用法
getJsonString({ name: "John Doe" }); // 示例
getJsonString([1, 2, 3]); // 示例
getJsonString("Hello World"); // 示例
进入全屏;退出全屏
泛型还允许你设定数据类型的限制条件。例如,如果你想创建一个只适用于具有ID的对象的函数,你可以这样实现:
const removeItemFromArray = <T extends { id: string }>(
array: T[],
id: string
) => {
const index = array.findIndex((item) => item.id === id);
if (index !== -1) {
array.splice(index, 1);
}
return array;
};
// 示例用法
removeItemFromArray(
[
{ id: "1", name: "John Doe" },
{ id: "2", name: "Jane Doe" },
],
"1"
); // 正确 // ✅
// 错误 // ❌
removeItemFromArray([1, 2, 3], "1");
全屏 退出全屏
7. 不可变类型不可变类型保证对象或数组中的数据不可被修改,从而避免意外的副作用,使代码更加可预测和易于调试。
你可以使用Readonly
和ReadonlyArray
来确保不可变性。
为对象使用 Readonly
interface User {
name: string;
age: number;
}
const user: 只读<User> = {
name: "John Doe",
age: 30,
};
user.name = "Jane Doe"; // ❌ 错误:无法为 'name' 赋值,因为它是一个只读属性
下面的代码展示了尝试更改只读属性时会出现的错误:
进入全屏模式:全屏
退出全屏模式:退出全屏
使用 ReadonlyArray
表示只读数组
const numbers: ReadonlyArray<number> = [1, 2, 3];
numbers.push(4); // ❌ 错误:只读数组类型上不存在属性 'push'
numbers[0] = 10; // ❌ 错误:只读数组不允许修改元素
切换到全屏 切换出全屏
深层次的不可变性
如果你需要深层次的不可变性(确保嵌套对象也保持不可变性),你可以使用这样的库,比如deep-freeze
,或者创建自定义工具类型。
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
interface User {
name: string;
address: {
city: string;
country: string;
};
}
const user: DeepReadonly<User> = {
name: "John Doe",
address: {
city: "New York",
country: "USA",
},
};
user.address.city = "Los Angeles"; // ❌ 错误:不能更改 'city',因为它是只读字段
点击全屏/退出全屏
你可以从将常量对象设为只读开始做起——有很多工具支持不可变模式的应用,比如,你可以在Redux
中使用时间旅行调试功能,但要详细解释这些工具,你可能需要更深入地研究。
Typescript 引入了几种实用类型,可以利用现有的类型生成具有特定特性的新类型。例如,这些实用类型在你需要生成一个与现有类型稍微有些不同的新类型时非常有用。
Pick
:从一个 对象类型 中挑选必要的属性。Omit
:从一个 对象类型 中选择所有属性,但排除 选定的键。Partial
:将一个 对象类型 中的所有属性设为可选的。Required
:将一个 对象类型 中的所有属性设为必填的。
interface User {
name: string;
age?: number;
email: string;
}
type PickUser = Pick<User, "name" | "age">;
type OmitUser = Omit<User, "age">;
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;
// PickUser 等同于:
// interface PickUser {
// name: string;
// age?: number;
// }
// OmitUser 等同于:
// interface OmitUser {
// name: string;
// email: string;
// }
// PartialUser 类型等同于:
// interface PartialUser {
// name?: string;
// age?: number;
// email?: string;
// }
// RequiredUser 类型等同于:
// interface RequiredUser {
// name: string;
// age: number;
// email: string;
// }
点击全屏观看,点击退出全屏
9. 联合体类型和交集类型如前所述,我们可以使用联合类型将2种或多种类型结合起来——联合类型是通过|
运算符来定义的,并将多种类型合并成一个单一类型。当你希望创建一个可以是多种不同类型之一的类型时,这会非常有用。
interface UnauthenticatedUser {
isAuthenticated: false;
}
interface AuthenticatedUser {
data: {
/* ... */
};
isAuthenticated: true;
}
// 联合类型 - 用户类型允许同时存储未认证用户和已认证用户
type User = UnauthenticatedUser | AuthenticatedUser;
全屏,退出全屏
&
运算符用于执行交集操作,将类型合并成一个单一类型。
接口 UserData {
name: string;
email: string;
phoneNumber: string;
}
接口 UserMetaData {
lastSignedInAt: Date;
signInLocation: {
country: string;
city: string;
};
}
type UserFullData = UserData & UserMetaData;
// UserFullData 等于:
// 接口 UserFullData {
// name: string;
// email: string;
// phoneNumber: string;
// lastSignedInAt: Date;
// signInLocation: {
// country: string;
// city: string;
// };
点击进入全屏;点击退出全屏
收尾
现在你知道了专业人士是如何使用TypeScript的!给自己点个赞吧。
好了,就这样🎉
谢谢阅读需要一位顶尖软件开发自由职业者来解决你的开发难题吗?请联系我,链接在这里:Upwork。
想看看我正在做什么吗?请查看我的个人网站和我的GitHub。
想联系我吗?可以在我LinkedIn上联系我。
可以来看看我的博客,获取每两周一次的新小贴士,可以在Medium上找到。
FAQ 常见问题
这些是我常接到的一些问题。希望这个常见问题解答(FAQ)能帮你解决问题。
-
我是前端开发的新手,应该怎么做?
参阅以下文章:, - 你会指导我吗?
抱歉,我现在手头上的工作已经很满了,抽不出时间来指导别人。
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章