[英]Infer string literal type from string manipulation
我有一个 function 接受一个字符串并创建一个 object 将四个 CRUD 操作映射到包含参数的“操作”字符串:
function createCrudActions(name: string) {
const record = name.toUpperCase();
return {
create: `CREATE_${record}`,
read: `READ_${record}`,
update: `UPDATE_${record}`,
delete: `DELETE_${record}`,
};
}
我不想让返回的 object 中的每个属性的类型为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 编译器不会将函数的返回值视为可分配给Actions<T>
- 对象的属性仍然是string
。 错误是:
输入'{创建:字符串; 读取:字符串; 更新:字符串; 删除:字符串; }' 不可分配给类型 'Actions<"post">'。 属性“创建”的类型不兼容。 类型 'string' 不可分配给类型 '"CREATE_POST"'。
我尝试在每个属性值以及返回的 object 本身上使用 const 断言( as const
),但属性类型仍然是字符串。 有没有什么方法可以做到这一点,而不仅仅是转换返回的 object ( 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") 的返回类型是:
{
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()
返回值转换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,
};
}
你知道为什么在这种情况下需要重载,而不是只指定 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
返回string
,而我们想对Uppercase<T>
进行操作。
但是为什么在这种情况下重载有效呢? Function 重载具有双变量作用,这意味着如果重载可分配给 function 类型签名,则它会编译,反之亦然。 当然,我们放松了类型严格性但获得了灵活性。
请参阅此示例,无需重载:
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
,因此 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>
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");
尝试在Actions
实用程序类型中添加额外的underscore
:
type Actions<T extends string> = {
[K in CrudActions]: `_${Uppercase<K>}_${Uppercase<T>}`;
}
现在,重载不能分配给 function 类型签名,因为没有一个类型不能相互分配。
但是,您可以将大写移动到单独的upercasing
。 这样,您将只创建一小段不安全的代码,而您的主要 function 将是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 能够自行推断所有类型
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.