从字符串操作推断字符串文字类型
Infer string literal type from string manipulation
我有一个函数,它接受一个字符串并创建一个对象,将四个 CRUD 操作映射到包含参数的“操作”字符串:
function createCrudActions(name: string) {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
};
}
我不想让 returned 对象中的每个 属性 的类型为 string
,而是想看看是否可以将它们设为字符串文字类型。我尝试使用模板文字类型来实现这一点:
type CrudActions = "create" | "read" | "update" | "delete";
type Actions<T extends string> = {
[K in CrudActions]: `${Uppercase<K>}_${Uppercase<T>}`;
}
function createCrudActions<T extends string>(name: T) {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
};
}
const postActions: Actions<"post"> = createCrudActions("post");
但是对于这段代码,TypeScript 编译器不会将函数的 return 值视为可分配给 Actions<T>
——对象的属性仍然是 string
。错误是:
Type '{ create: string; read: string; update: string; delete: string;
}' is not assignable to type 'Actions<"post">'. Types of property
'create' are incompatible.
Type 'string' is not assignable to type '"CREATE_POST"'.
我尝试对每个 属性 值以及 returned 对象本身使用 const 断言 (as const
),但是 属性 类型仍然是字符串.有什么方法可以不只转换 returned 对象 (as Actions<T>
) 来做到这一点?如果是这样,那将有点违背目的,所以我希望有某种方法可以让编译器理解。但我认为它可能无法确定运行时 toUpperCase
调用对应于 Actions<T>
.
定义中的 Uppercase
转换
编辑:另一种接近但不完全是我想要的方法:
type CrudActions = "create" | "read" | "update" | "delete";
type ActionCreator = (s: string) => { [K in CrudActions]: `${Uppercase<K>}_${Uppercase<typeof s>}` };
const createCrudActions: ActionCreator = <T extends string>(name: T) => {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
};
}
const postActions = createCrudActions("post");
但在这种情况下,`createCrudActions("post") 的 return 类型是:
{
create: `CREATE_${record}`;
read: `READ_${record}`;
update: `UPDATE_${record}`;
delete: `DELETE_${record}`;
}
而我希望它是:
{
create: `CREATE_POST`;
read: `READ_POST`;
update: `UPDATE_POST`;
delete: `DELETE_POST`;
}
我哄骗它在所有属性 和 上使用 as const
铸造 toUpperCase()
return 值 as Uppercase<T>
;不过,在这一点上,它并不比 as Actions<T>
好多少。从技术上讲,这验证了转换是正确的,但它保护的代码不太可能更改,并且使用它的代码同样受到类型错误的保护。
function createCrudActions<T extends string>(name: T) {
const record = name.toUpperCase() as Uppercase<T>;
return {
create: `CREATE_${record}` as const,
read: `READ_${record}` as const,
update: `UPDATE_${record}` as const,
delete: `DELETE_${record}` as const,
};
}
Do you know why the overload is required in this case, as opposed to just specifying the return type of the function?
考虑这个例子:
type CrudActions = "create" | "read" | "update" | "delete";
type Actions<T extends string> = {
[K in CrudActions]: `${Uppercase<K>}_${Uppercase<T>}`;
}
function createCrudActions<T extends string>(name: T):Actions<T> {
/**
* toUpperCase returns string instead of Uppercase<T>,
* hence `CREATE_${record}` is now `CREATE_${string}` whereas you
* want it to be `CREATE_${Uppercase<T>}`
*/
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
} as const; // error
}
const postActions = createCrudActions("post").create;
很清楚为什么我们这里有错误。因为 toUpperCase
returns string
而我们要对 Uppercase<T>
.
进行操作
但为什么重载在这种情况下有效?函数重载是双变的,这意味着如果 overdload 可分配给函数类型签名,它就会编译,反之亦然。当然,我们放宽了类型严格性但获得了灵活性。
看这个例子,没有重载:
function createCrudActions<T extends string>(name: T) {
const record = name.toUpperCase();
const result = {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
} as const;
return result
}
const result = createCrudActions("post");
type Check1<T extends string> = typeof result extends Actions<T> ? true : false
type Check2<T extends string> = Actions<T> extends typeof result ? true : false
type Result = [Check1<'post'>, Check2<'post'>]
Result
是 [false, true]
。由于 Result
至少有一个 true
,函数重载应该可以工作。
重载版本:
type CrudActions = "create" | "read" | "update" | "delete";
type Actions<T extends string> = {
[K in CrudActions]: `${Uppercase<K>}_${Uppercase<T>}`;
}
function createCrudActions<T extends string>(name: T): Actions<T>
function createCrudActions<T extends string>(name: T) {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
} as const;
}
const result = createCrudActions("post");
尝试将额外的 underscore
添加到 Actions
实用程序类型:
type Actions<T extends string> = {
[K in CrudActions]: `_${Uppercase<K>}_${Uppercase<T>}`;
}
现在,重载不能分配给函数类型签名,因为所有类型都不能相互分配。
但是,您可以将 upercasing
移动到一个单独的函数。通过这种方式,您将只创建一小段不安全代码,而您的主要功能将是 safe
。当我说 safe
时,我的意思是:as much as TS allows it to be safe
.
type CrudActions = "create" | "read" | "update" | "delete";
const uppercase = <T extends string>(str: T) => str.toUpperCase() as Uppercase<T>;
function createCrudActions<T extends string>(name: T) {
const record = uppercase(name)
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
} as const;
}
const result = createCrudActions("post").create
现在您甚至不需要 Actions
类型,因为 TS 能够自行推断出所有类型
我有一个函数,它接受一个字符串并创建一个对象,将四个 CRUD 操作映射到包含参数的“操作”字符串:
function createCrudActions(name: string) {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
};
}
我不想让 returned 对象中的每个 属性 的类型为 string
,而是想看看是否可以将它们设为字符串文字类型。我尝试使用模板文字类型来实现这一点:
type CrudActions = "create" | "read" | "update" | "delete";
type Actions<T extends string> = {
[K in CrudActions]: `${Uppercase<K>}_${Uppercase<T>}`;
}
function createCrudActions<T extends string>(name: T) {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
};
}
const postActions: Actions<"post"> = createCrudActions("post");
但是对于这段代码,TypeScript 编译器不会将函数的 return 值视为可分配给 Actions<T>
——对象的属性仍然是 string
。错误是:
Type '{ create: string; read: string; update: string; delete: string; }' is not assignable to type 'Actions<"post">'. Types of property 'create' are incompatible. Type 'string' is not assignable to type '"CREATE_POST"'.
我尝试对每个 属性 值以及 returned 对象本身使用 const 断言 (as const
),但是 属性 类型仍然是字符串.有什么方法可以不只转换 returned 对象 (as Actions<T>
) 来做到这一点?如果是这样,那将有点违背目的,所以我希望有某种方法可以让编译器理解。但我认为它可能无法确定运行时 toUpperCase
调用对应于 Actions<T>
.
Uppercase
转换
编辑:另一种接近但不完全是我想要的方法:
type CrudActions = "create" | "read" | "update" | "delete";
type ActionCreator = (s: string) => { [K in CrudActions]: `${Uppercase<K>}_${Uppercase<typeof s>}` };
const createCrudActions: ActionCreator = <T extends string>(name: T) => {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
};
}
const postActions = createCrudActions("post");
但在这种情况下,`createCrudActions("post") 的 return 类型是:
{
create: `CREATE_${record}`;
read: `READ_${record}`;
update: `UPDATE_${record}`;
delete: `DELETE_${record}`;
}
而我希望它是:
{
create: `CREATE_POST`;
read: `READ_POST`;
update: `UPDATE_POST`;
delete: `DELETE_POST`;
}
我哄骗它在所有属性 和 上使用 as const
铸造 toUpperCase()
return 值 as Uppercase<T>
;不过,在这一点上,它并不比 as Actions<T>
好多少。从技术上讲,这验证了转换是正确的,但它保护的代码不太可能更改,并且使用它的代码同样受到类型错误的保护。
function createCrudActions<T extends string>(name: T) {
const record = name.toUpperCase() as Uppercase<T>;
return {
create: `CREATE_${record}` as const,
read: `READ_${record}` as const,
update: `UPDATE_${record}` as const,
delete: `DELETE_${record}` as const,
};
}
Do you know why the overload is required in this case, as opposed to just specifying the return type of the function?
考虑这个例子:
type CrudActions = "create" | "read" | "update" | "delete";
type Actions<T extends string> = {
[K in CrudActions]: `${Uppercase<K>}_${Uppercase<T>}`;
}
function createCrudActions<T extends string>(name: T):Actions<T> {
/**
* toUpperCase returns string instead of Uppercase<T>,
* hence `CREATE_${record}` is now `CREATE_${string}` whereas you
* want it to be `CREATE_${Uppercase<T>}`
*/
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
} as const; // error
}
const postActions = createCrudActions("post").create;
很清楚为什么我们这里有错误。因为 toUpperCase
returns string
而我们要对 Uppercase<T>
.
但为什么重载在这种情况下有效?函数重载是双变的,这意味着如果 overdload 可分配给函数类型签名,它就会编译,反之亦然。当然,我们放宽了类型严格性但获得了灵活性。
看这个例子,没有重载:
function createCrudActions<T extends string>(name: T) {
const record = name.toUpperCase();
const result = {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
} as const;
return result
}
const result = createCrudActions("post");
type Check1<T extends string> = typeof result extends Actions<T> ? true : false
type Check2<T extends string> = Actions<T> extends typeof result ? true : false
type Result = [Check1<'post'>, Check2<'post'>]
Result
是 [false, true]
。由于 Result
至少有一个 true
,函数重载应该可以工作。
重载版本:
type CrudActions = "create" | "read" | "update" | "delete";
type Actions<T extends string> = {
[K in CrudActions]: `${Uppercase<K>}_${Uppercase<T>}`;
}
function createCrudActions<T extends string>(name: T): Actions<T>
function createCrudActions<T extends string>(name: T) {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
} as const;
}
const result = createCrudActions("post");
尝试将额外的 underscore
添加到 Actions
实用程序类型:
type Actions<T extends string> = {
[K in CrudActions]: `_${Uppercase<K>}_${Uppercase<T>}`;
}
现在,重载不能分配给函数类型签名,因为所有类型都不能相互分配。
但是,您可以将 upercasing
移动到一个单独的函数。通过这种方式,您将只创建一小段不安全代码,而您的主要功能将是 safe
。当我说 safe
时,我的意思是:as much as TS allows it to be safe
.
type CrudActions = "create" | "read" | "update" | "delete";
const uppercase = <T extends string>(str: T) => str.toUpperCase() as Uppercase<T>;
function createCrudActions<T extends string>(name: T) {
const record = uppercase(name)
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
} as const;
}
const result = createCrudActions("post").create
现在您甚至不需要 Actions
类型,因为 TS 能够自行推断出所有类型