TypeScript 最佳实践:让你的代码不再“撞墙”
今天和大家分享一些 TypeScript 最佳实践,这些实践不仅能让你的代码更加优雅,还能避免一些常见的“撞墙”问题。废话不多说,让我们开始吧!
使用 strictNullChecks
在 TypeScript 中,null 和 undefined 是默认可以赋值给任何类型的,这很容易导致一些潜在的 bug。使用 strictNullChecks 可以让 TypeScript 在编译时检查 null 和 undefined 的使用情况,避免这些问题的发生。
// 不使用 strictNullChecks
let foo: string = null; // 编译通过,运行时报错
// 使用 strictNullChecks
let bar: string | null = null; // 编译时报错,需要显式声明可能为 null 的变量类型
使用 readonly
在 TypeScript 中,可以使用 readonly 关键字将属性设置为只读,这样在编译时就能避免一些不必要的修改。
class Foo {
public readonly bar: string = "hello";
}
const foo = new Foo();
foo.bar = "world"; // 编译时报错,无法修改 readonly 属性
使用 namespace
在 TypeScript 中,可以使用 namespace 将相关的代码组织在一起,避免命名冲突和全局变量污染。
namespace Foo {
export function bar() {
console.log("hello");
}
}
Foo.bar(); // 输出 hello
使用 interface
在 TypeScript 中,可以使用 interface 定义对象的结构,这样可以提高代码的可读性和可维护性。
interface User {
name: string;
age: number;
}
function greet(user: User) {
console.log(`Hello, ${user.name}! You are ${user.age} years old.`);
}
const user = { name: "Alice", age: 18 };
greet(user); // 输出 Hello, Alice! You are 18 years old.
使用 type
在 TypeScript 中,可以使用 type 定义类型别名,这样可以提高代码的可读性和可维护性。
type User = {
name: string;
age: number;
};
function greet(user: User) {
console.log(`Hello, ${user.name}! You are ${user.age} years old.`);
}
const user = { name: "Alice", age: 18 };
greet(user); // 输出 Hello, Alice! You are 18 years old.
使用泛型
在 TypeScript 中,可以使用泛型提高代码的灵活性和复用性。
function identity<T>(arg: T): T {
return arg;
}
console.log(identity("hello")); // 输出 hello
console.log(identity(123)); // 输出 123
使用枚举
在 TypeScript 中,可以使用枚举提高代码的可读性和可维护性。
enum Color {
Red,
Green,
Blue,
}
console.log(Color.Red); // 输出 0
console.log(Color.Green); // 输出 1
console.log(Color.Blue); // 输出 2
使用装饰器
在 TypeScript 中,可以使用装饰器扩展类和方法的功能。
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`[${new Date().toISOString()}] ${propertyKey}(${args})`);
return originalMethod.apply(this, args);
};
}
class Foo {
@log
bar() {
console.log("hello");
}
}
const foo = new Foo();
foo.bar(); // 输出 [2021-10-01T00:00:00.000Z] bar()
// hello
使用 Promise
在 TypeScript 中,可以使用 Promise 处理异步操作,避免回调地狱和错误处理问题。
function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("hello");
}, 1000);
});
}
async function main() {
try {
const data = await fetchData();
console.log(data); // 输出 hello
} catch (error) {
console.error(error);
}
}
main();
使用 async/await
在 TypeScript 中,可以使用 async/await 处理异步操作,使代码更加简洁和可读。
function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("hello");
}, 1000);
});
}
async function main() {
const data = await fetchData();
console.log(data); // 输出 hello
}
main();
使用模块化
在 TypeScript 中,可以使用模块化将代码组织起来,避免全局变量污染和命名冲突。
// greeter.ts
export function greet(name: string) {
console.log(`Hello, ${name}!`);
}
// main.ts
import { greet } from "./greeter";
greet("Alice"); // 输出 Hello, Alice!
使用命名空间别名
在 TypeScript 中,可以使用命名空间别名提高代码的可读性和可维护性。
namespace Foo.Bar.Baz {
export function qux() {}
}
import baz = Foo.Bar.Baz;
baz.qux();
使用类型断言
在 TypeScript 中,可以使用类型断言将一个值断言为某个类型,避免一些类型推导问题。
interface User {
name: string;
age: number;
}
const user: any = { name: "Alice", age: "18" };
console.log((user as User).name); // 输出 Alice
console.log((user as User).age); // 输出 undefined(因为 age 是字符串类型)
使用非空断言
在 TypeScript 中,可以使用非空断言告诉编译器一个变量不会是 null 或 undefined。
let foo!: string;
console.log(foo.length); // 不会报错(因为 foo 已经被标记为非空)
使用 unknown
在 TypeScript 中,可以使用 unknown 表示一个未知类型的值,这样可以提高代码的健壮性和可维护性。
function foo(input: unknown) {
if (typeof input === "string") {
console.log(input.toUpperCase());
} else if (Array.isArray(input)) {
console.log(input.join(","));
} else if (typeof input === "object" && input !== null) {
console.log(Object.keys(input));
} else {
console.log("unknown");
}
}
foo("hello"); // 输出 HELLO
foo([1,2,3]); // 输出 1,2,3
foo({ a: 1, b: 2 }); // 输出 a,b
foo(null); // 输出 unknown
使用 as const
在 TypeScript 中,可以使用 as const 将一个对象或数组中的所有值都标记为 readonly 和字面量类型。
const foo = {
bar: "hello",
} as const;
foo.bar = "world"; // 编译时报错,无法修改 readonly 属性
const bar = [1,2,3] as const;
bar.push(4); // 编译时报错,无法修改数组长度
使用 keyof
在 TypeScript 中,可以使用 keyof 获取一个对象的所有键名。
interface User {
name: string;
age: number;
}
type UserKey = keyof User;
const key: UserKey = "name"; // 编译通过
const key2: UserKey = "email"; // 编译时报错,email 不是 User 的键名之一
使用 in
在 TypeScript 中,可以使用 in 判断一个属性是否存在于一个对象中。
interface User {
name?: string;
age?: number;
}
function greet(user: User) {
if ("name" in user) {
console.log(`Hello, ${user.name}!`);
} else if ("age" in user) {
console.log(`You are ${user.age} years old.`);
} else {
console.log("Unknown user.");
}
}
greet({ name: "Alice" }); // 输出 Hello, Alice!
greet({ age: 18 }); // 输出 You are 18 years old.
greet({}); // 输出 Unknown user.
使用 as unknown as T
在 TypeScript 中,可以使用 as unknown as T 将一个值断言为某个类型,并且避免类型检查错误。
const data = JSON.parse('{"name": "Alice", "age": "18"}');
console.log((data as unknown as User).name); // 输出 Alice(因为 age 是字符串类型)
console.log((data as unknown as User).age); // 输出 undefined(因为 age 是字符串类型)
使用 Partial
在 TypeScript 中,可以使用 Partial 将一个对象中所有属性都变成可选属性。
interface User {
name: string;
age: number;
}
function updateUserInfo(user: Partial<User>) {}
updateUserInfo({}); // 编译通过(因为所有属性都是可选的)
updateUserInfo({ name: "Alice" }); // 编译通过(因为所有属性都是可选的)
updateUserInfo({ name: "Alice", age: 18 }); // 编译通过(因为所有属性都是可选的)
希望这些实践能够帮助大家写出更加优雅、健壮和可维护的代码。如果你有其他好的实践,欢迎留言分享给大家!
转载自:https://juejin.cn/post/7226930744769626169