一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情。
本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧31:尽量不要null值和非null混用
null值和非null混用会有很多麻烦,完全null值或者完全非null值都比较好处理。
例如:你想写一个extent函数计算一个number数组的最大值和最小值:
function extent(nums: number[]) {
let min, max;
for (const num of nums) {
if (!min) {
min = num;
max = num;
} else {
min = Math.min(min, num);
max = Math.max(max, num);
}
}
return [min, max];
}
在没有strictNullChecks下,这段代码都能通过检查,并且返回值类型为 number[]。但是这段代码有bug和设计缺陷:
- 如果min或者max值为0,那么就会出现错误,例如extent([0,1,2]) 会返回[1,2],而不是[0,2]
- 如果nums为[], 将会返回[undefined,undefined],这种将undefined / null 值混用的行为,会给后面使用这个函数的人带来很多麻烦
将strictNullChecks 打开,将会报错:
function extent(nums: number[]) {
let min, max;
for (const num of nums) {
if (!min) {
min = num;
max = num;
} else {
min = Math.min(min, num);
max = Math.max(max, num);
// ~~~ Argument of type 'number | undefined' is not
// assignable to parameter of type 'number'
}
}
return [min, max];
}
这人代码设计缺陷暴露的更明显。当你调用该函数,会出现如下问题:
const [min, max] = extent([0, 1, 2]);
const span = max - min;
// ~~~ ~~~ Object is possibly 'undefined'
extent函数中报错的原因:你通过判断保证min不为undefined,但是没有对max进行undefined检查。、
一个更好的做法:
function extent(nums: number[]) {
let result: [number, number] | null = null;
for (const num of nums) {
if (!result) {
result = [num, num];
} else {
result = [Math.min(num, result[0]), Math.max(num, result[1])];
}
}
return result;
}
返回值类型为:[number, number] | null,调用者将非常方便去处理:
- 用非空断言:
const [min, max] = extent([0, 1, 2])!; const span = max - min; // OK - 用一个检查:
const range = extent([0, 1, 2]); if (range) { const [min, max] = range; const span = max - min; // OK }
null值非null值混用同样会在classes中导致问题的发生:
lass UserPosts {
user: UserInfo | null;
posts: Post[] | null;
constructor() {
this.user = null;
this.posts = null;
}
async init(userId: string) {
return Promise.all([
async () => this.user = await fetchUser(userId),
async () => this.posts = await fetchPostsForUser(userId)
]);
}
getUserName() {
// ...?
}
}
当在请求接口的时候,user和posts将会为null。有时,两者都可能为null,也有可能其中一个为null,也有可能两个都为非null。这让使用这个class的人非常痛苦。
更好的做法:当在请求接口的时候,用 await等待,直到所有的值都可用:
class UserPosts {
user: UserInfo;
posts: Post[];
constructor(user: UserInfo, posts: Post[]) {
this.user = user;
this.posts = posts;
}
static async init(userId: string): Promise<UserPosts> {
const [user, posts] = await Promise.all([
fetchUser(userId),
fetchPostsForUser(userId)
]);
return new UserPosts(user, posts);
}
getUserName() {
return this.user.name;
}
}
现在 UserPosts类就是完全的非空,非常好使用。当然你非要在数据加载过程中,强行执行某些操作,判断非空也很方便。