写出更优雅和稳健的 TS 代码的几个 tips
本来想放优雅 太优雅了.jpg,后来还是好懒啊……
使用 unknown 代替 any
any
的问题在于它直接关闭了 TS 的类型检查,因此一旦使用了 any
,那就代表任何事情都会发生。使用 unknown
则告诉 TS:在目前这个步骤我还不知道使用的数据类型,不过当我去使用该变量的时候,我就知道这个类型是什么了(多为类型检查)。
参考一下代码:
interface IUser {
id: number;
firstname: string;
lastname: string;
gender: string;
image: string;
age: number;
}
interface IAdminUser extends IUser {
token: string;
addNewUser: () => void;
}
const isAdmin = (object: unknown): boolean => {
return true;
};
const fetchUser = async () => {
const res = await fetch('http://localhost:3000/users/1');
// bad ❌
const badUser = await res.json();
console.log(badUser.nonExistingProp);
// good ✅
const goodUser: unknown = await res.json();
console.log(goodUser.nonExistingProp);
// add type guard
if (isAdmin(goodUser)) {
}
};
直接使用 any
的代码不会出现任何的报错,而使用 unknown
则是会强制要求进行类型检查才能继续。
使用 is
is
也是一种类型断言,稍微修改一下上面的代码中对 user 的检查:
const isAdmin = (object: unknown): object is IAdminUser => {
if (object !== null && typeof object === 'object') {
return 'token' in object;
}
return false;
};
const isRegularUser = (object: unknown): object is IUser => {
if (object !== null && typeof object === 'object') {
return 'token'! in object;
}
return false;
};
在调用时,TS 就会遵从 object is IAdminUser
,在返回值为 true 的时候断言返回类型为 IAdminUser,这时候 goodUser 中就会包含所有管理员权限应该有的属性和函数。
同理,如果检查下来用户不是管理员,那么该用户也就无法调用管理员才能使用的属性和函数:
另一种需要 is
的情况是 Union Type,Union Type 的定义也是 type 乱用的重灾区。
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
// The predicate may need repeating for more complex examples
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === 'sharkey') return false;
return isFish(pet);
});
可能对于单独一个对象而已,使用 as
进行 type casting 问题不是很大,不过对于之后要重新声明的数组等,一直使用 as
去转确实挺麻烦的。不过有了现在这个方法,就可以忽略掉 as Type[]
这种声明了。
使用 satisfies 关键词
satisfies
是 4.9 新出的一个特性,已下面代码为案例:
interface ICustomImage {
width: number;
height: number;
data: string;
}
interface IUser {
id: number;
firstname: string;
lastname: string;
image: string | ICustomImage;
}
const user: IUser = {
id: 1,
firstname: '',
lastname: '',
image: 'random url',
};
可以看到,尽管声明的是一个字符串,不过因为 union type 的关系,TS 无法明确认识 user.image
究竟是字符串还是对象,但是使用 satisfies
关键词之后则是另一个情况:
TS 能够更好的通过数据类型进行断言和提示。
正确使用 enum
这里指的是尽量不要使用默认的 enum 类型(即数字),如:
尽管 100 不在 State 中(默认应该从 0 开始,所以这里只有 0,1,2),但是传参时检查失败。不过使用字符串就可以避免这个问题了:
善用 Utility Type
这里提一个我刚找到能够很好的解决我目前一个 CRUD 操作的组合技:
interface IUser {
id: number;
firstname: string;
lastname: string;
gender: string;
image: string;
age: number;
}
const updateUser = (
userId: IUser['id'],
updatedUser: Partial<Omit<IUser, 'id'>>
) => {};
这时候查看就能够发现,所有的选项已经变成了 optional,而 id 已经从可更新的状态中移除了。
Util Types真的还蛮有用的……不过我之前看的一些教程都没讲,晚点这块继续补一下吧。
参考
- Using type predicates