在 ts 中,用 const 声明变量只能确保该变量的引用不会改变,但变量属性仍然可变:
const user = {
name:'Tom',
age:18
};
// 并不会报错
user.name = 'Jerry';
我们在 TypeScript Playground[1] 中观察上述代码生成的 .d.ts 文件,看到如下结果:
declare const user: {
name: string;
age: number;
};
因为 user.name 是 string 类型,所以重新赋值 ‘Jerry’ 并不会报错。
下面分析几种常见的需求场景,以及对应的解决方法:
-
如果想让 user.name 不可变,怎么办?
第一个办法是,将 user.name 变成常量:
const user = {
name: 'Tom' as 'Tom',
age: 18
};
此时再运行上面的赋值语句就会报错:
而 user 变量的类型也变成了这样:
declare const user: {
name: "Tom";
age: number;
};
问题来了,如果一个对象有很多个属性都不可变,难道要像下面这样写吗:
const HTTPRequestMethod = {
CONNECT: "CONNECT" as "CONNECT",
DELETE: "DELETE" as "DELETE",
GET: "GET" as "GET",
HEAD: "HEAD" as "HEAD",
OPTIONS: "OPTIONS" as "OPTIONS",
PATCH: "PATCH" as "PATCH",
POST: "POST" as "POST",
PUT: "PUT" as "PUT",
TRACE: "TRACE" as "TRACE"
};
程序员都不喜欢做重复的事,上面的写法其实可以简化成这样:
const HTTPRequestMethod = {
CONNECT: "CONNECT" as const,
DELETE: "DELETE" as const,
...
};
即,用 as const 替代前面的属性值以避免重复书写。
如果一个对象的全部属性都是不可变的,我们甚至可以简化成这样:
const user = {
name: 'Tom',
age: 18
} as const;
此时,对 name 或 age 的改动都会报错。
-
怎么在声明类型时,设置属性不可变?
观察下面对象的 .d.ts 输出:
const user = {
name:'Tom',
age:18
} as const;
得到的是:
declare const user: {
readonly name: "Tom";
readonly age: 18;
};
所以,要将类型的某个属性设置为不可变,加上 readonly 修饰即可。如:
type User = {
readonly name: string;
}
const me: User = {
name: 'Youmoo'
};
-
怎么复用一个已经存在的类型,让其属性变为不可变?
比如,我们想把如下类型
type User = {
name: string;
}
变成
type User = {
readonly name: string;
}
TypeScript 为此提供一个 Readonly<Type> 辅助类型,只需这样用:
type UserNew = Readonly<User>;
const user: UserNew = ...;
有了以上基础,我们再说一个更复杂的应用。
假设页面上有一组静态菜单(menus),每个菜单有菜单名(name)和跳转路径(link)。要求我们创建一个从菜单名到菜单的映射,怎么做?
一开始,我们的做法可能是这样的:
type Menu = {
name: string;
link: string
};
const menus: Menu[] = [
{
name: 'Home',
link: '/',
},
{
name: 'About',
link: '/about',
}
];
type Mapping = {
[key: string]: Menu
}
const mappings: Mapping = menus.reduce((p, v) => {
p[v.name] = v;
return p;
}, {} as Mapping);
初看没什么问题,但我们发现下面代码并不会报错:
// 接着上面
// 这行代码并不会报错
const homeMenu = mappings.Homeee;
原因很简单,因为 mappings 的属性是 string 类型。但是,我们也知道,其实我们允许的属性只有 Home 和 About。
怎么让 ts 检查到这种错误呢?
第一种办法比较笨,但能解决问题,就是手动列举出支持的属性:
type Menu = {
name: string;
link: string
};
const menus: Menu[] = [
{
name: 'Home',
link: '/',
},
{
name: 'About',
link: '/about',
}
];
type Mapping = {
[key in 'Home' | 'About']: Menu
}
const mappings: Mapping = menus.reduce((p, v) => {
p[v.name] = v;
return p;
}, {} as Mapping);
// 接着上面
// 这行代码并不会报错
const homeMenu = mappings.Homeee;
观察编译提示,我们发现 ts 检测到了错误:
但上图中又出现了另一处错误(reduce函数内的赋值),原因是 v.name 是 string 类型,而 p 的属性是 ‘Home’ | ‘About’ 类型,导致类型不匹配。
怎么办,只能 workaroud 一下,对 v.name 进行类型转换:
// 注意这里
type MenuName = 'Home' | 'About';
type Mapping = {
[key in MenuName]: Menu
}
const mappings: Mapping = menus.reduce((p, v) => {
// 以及这里
p[v.name as MenuName] = v;
return p;
}, {} as Mapping);
上面说了,这种办法比较笨。一是,需要列举出所有菜单名,二是,需要类型转换。在代码编写中,凡是需要手动列举的东西,难免会出出错,因为要保证你列举的菜单名和实际菜单名的一致性。
所以有了第二种更合适的办法,复用 menus 对象中的菜单名:
const menus = [
{
name: 'Home',
link: '/',
},
{
name: 'About',
link: '/about',
}
] as const;// 1. 注意这里
// 2. 注意这里
type Menu = typeof menus[number];
type MenuName = typeof menus[number]['name'];
type Mapping = {
[key in MenuName]: Menu
}
const mappings: Mapping = menus.reduce((p, v) => {
// 不需要类型转换了
p[v.name] = v;
return p;
}, {} as Mapping);
通过以上代码,我们消除了 手动列举菜单名 和 强制类型转换 两个弊端。
如果你仍然不完全理解上述代码是如何运作的,访问这个示例[2],依次将鼠标划过相关的变量和类型,看看 ts 是如何解释它们的。
另外 Mapping 这个类型还能这样写:
type Mapping = Record<MenuName, Menu>;
欢迎阅读,点个【在看】支持下吧。
参考资料
TypeScript Playground: https://www.typescriptlang.org/play
[2]Playground 示例: https://www.typescriptlang.org/play?#code/MYewdgzgLgBAtgUzAVwjAvDA2gKBvmAbzwNLAENEAuGAcgAkRFaAaE0-AGwEswBrGrQD0rdjAC+bUsQ4EK1OgEEARiGRRRsrrwF0h5Ves0FxOALoxyaUJCgBuIUJgBGAHQxAFzaB4Q0Cb8YBnEnBxHGAAmd29-HCgATwAHBBgAWSRkDBgY+JAAM3gUiCwUOGUEACczOyi4hOSUADlKBMwMhGzclHzC4rKsWnkEWnLA5qTyWNjeAHM0mQIsPgRomF4klPrEMxoa5BxTG2h4UfGwCYhNw8m0xHbXEoQAE2RgBAAKZ9iWGAA3AEoMAD4iGJgoBYOUAAOmAQMjAN4+gGj1QA28YAjY0AYXJiWJYT6uPoWTCfCqkW5QZAlMAwWIVSREcSWNCJc7Hb4VQIgwAa2oAKdUAIW6AHeDAKrKgBh-wACRoBTuUApUaATFScHBaSdXIxEAgEEA
– END –
原文始发于微信公众号(背井):TypeScript 常量使用技巧
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/246813.html