×

阅读代码:strapi / nextjs-corporate-starter

2024-03-19 13:25:37 Falcon

Q:解释一下这个 typescript脚本

export const i18n = {
defaultLocale: 'en',
locales: ['en', 'de', 'cs'],
} as const;

export type Locale = typeof i18n['locales'][number];

该 TypeScript 脚本定义了一个用于国际化(i18n)的配置,以及一个关联的类型定义。 这个脚本可以分解为两个部分:

  1. i18n 对象的定义:
export const i18n = {
    defaultLocale: 'en',
    locales: ['en', 'de', 'cs'],
} as const;

在这里,i18n 对象包括两个属性:defaultLocale 定义了默认的区域设置,此处为 'en'(代表英语);locales 属性是一个字符串数组,列出了支持的区域设置代码,表示应用程序支持英语、德语和捷克语。 通过使用 as const 修饰符,我们将 i18n 对象中的每一个属性都显式地标记为常量,这会使得 TypeScript 对该对象进行更为严格的类型检查,该对象的属性将不能被更改(immutable),并且 locales 数组中的值也将被视为字面量类型而非普通的字符串。

  1. Locale 类型的定义:
export type Locale = typeof i18n['locales'][number];

Locale 是一个通过索引访问操作符([number])从 i18n['locales'] 数组中提取出的类型。它相当于 i18n.locales 数组中所有可能值的联合类型,于是 Locale 类型会等同于 'en' | 'de' | 'cs'。这意味着任何 Locale 类型的变量在赋值时只能被赋予 'en''de''cs' 这三个字符串字面量中的一个。 简单来说,此脚本在声明了 app 支持的语言列表的同时,定义了一个类型,这个类型仅限于这些已声明的语言代码。

Q:export type Locale = typeof i18n['locales'][number]; 这里的number是什么?

在 TypeScript 中,当你在索引类型时使用 number 关键字,你实际上是在说:“使用任何数字来索引数组,得到数组中某个元素的类型。” 于是,typeof i18n['locales'][number] 这种用法意味着你想获取 i18n['locales'] 数组中任何一个元素的类型,即,数组中每个元素的类型的联合。 在你提供的上下文中,i18n['locales'] 被定义为具有具体值的字符串数组,使用 as const 进行类型施加,让 TypeScript 将其理解为一个具体值的数组,而不是任意字符串的数组。这样,当你用 number 来索引这样一个数组,你得到的是各个具体字符串值的联合类型,而不是普通的 string 类型。也就是说,对于数组 ['en', 'de', 'cs'] 而言,typeof i18n['locales'][number] 的结果是类型 'en' | 'de' | 'cs',它表示任何赋值给该类型的变量都必须具有数组中的一个字符串值。

TypeScript 强大的功能之一就是你可以定义一个类型,这个类型严格对应于一个字面量数组中的元素。下面是使用这种方式的更多例子:

示例1:状态码

设想你的应用程序中有一组状态码,用于代表不同的状态:

const statusCodes = ['loading', 'success', 'error'] as const;
type StatusCode = typeof statusCodes[number];

然后你可以使用 StatusCode 类型确保你的函数或变量只接受这些指定的状态码:

function updateStatus(status: StatusCode) {
  // 函数体在这里
}
updateStatus('loading'); // 有效
updateStatus('error'); // 有效
// updateStatus('not_found'); // TypeScript错误:“not_found”无法分配给类型'StatusCode'

示例2:配置标志

假设你有一组功能标志,用于在应用程序中切换特定功能:

const featureFlags = ['beta', 'new_ui', 'experimental_api'] as const;
type FeatureFlag = typeof featureFlags[number];

在传递功能标志时:

function enableFeature(flag: FeatureFlag) {
  // 启用功能
}
enableFeature('new_ui'); // 正确的用法
// enableFeature('alpha'); // 无效用法,会导致TypeScript错误

示例3:基于角色的访问控制

考虑到一个应用程序具有基于角色的访问系统:

const userRoles = ['admin', 'editor', 'subscriber'] as const;
type UserRole = typeof userRoles[number];

然后你可以强制进行角色检查:

function performRestrictedAction(userRole: UserRole) {
  if (userRole === 'admin') {
    // 执行操作
  }
}
performRestrictedAction('editor'); // 有效角色
// performRestrictedAction('guest'); // 类型错误...

以上例子描述了如何在 TypeScript 中创建并使用联合类型,以确保变量和函数参数与预定义的字符串字面量集合严格匹配。

Q: typescript 声明一个对象为常量,如果不使用as const关键字,其内部的属性仍然是可以改变的?是吗

是的,当你在 TypeScript 中声明一个对象时,如果不使用 as const 断言,对象内部的属性是可以修改的。as const 断言的作用是将一个对象标记为完全不可更改的常量。 在不使用 as const 的情况下,TypeScript 默认认为对象的属性是可变的。例如:

const config = {
  endpoint: '<http://api.example.com>',
  retries: 3,
};
// 没有使用 as const ,所以属性可以被修改
config.endpoint = '<http://api.changedexample.com>'; // 正常工作
config.retries = 5; // 正常工作

在以上代码中,config 对象的 endpointretries 属性均可被赋予新值。 然而,当使用了 as const 之后,对象及其所有嵌套的属性都会被视为字面量类型,并且不能被更改:

const config = {
  endpoint: '<http://api.example.com>',
  retries: 3,
} as const;
// 使用了 as const ,试图修改属性会导致错误
config.endpoint = '<http://api.changedexample.com>'; // TypeScript错误:无法分配到 'endpoint' ,因为它是只读属性。
config.retries = 5; // TypeScript错误:无法分配到 'retries' ,因为它是只读属性。

在这种情况下,as const 告诉 TypeScript 编译器对象 config 及其属性是不可更改的,任何试图修改这些属性的操作都会导致编译时错误。

本文收录于