简体   繁体   English

从字符串操作推断字符串文字类型

[英]Infer string literal type from string manipulation

I've got a function that takes a string and creates an object mapping the four CRUD actions to "action" strings containing the argument:我有一个 function 接受一个字符串并创建一个 object 将四个 CRUD 操作映射到包含参数的“操作”字符串:

function createCrudActions(name: string) {
  const record = name.toUpperCase();

  return {
    create: `CREATE_${record}`,
    read: `READ_${record}`,
    update: `UPDATE_${record}`,
    delete: `DELETE_${record}`,
  };
}

Rather than have the type of each property in the returned object be string , I wanted to see if I could make them string literal types.我不想让返回的 object 中的每个属性的类型为string ,而是想看看是否可以将它们设为字符串文字类型。 I tried using template literal types to achieve this:我尝试使用模板文字类型来实现这一点:

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");

But with this code, the TypeScript compiler doesn't see the function's return value as assignable to Actions<T> —the object's properties are still string .但是使用此代码,TypeScript 编译器不会将函数的返回值视为可分配给Actions<T> - 对象的属性仍然是string The error is:错误是:

Type '{ create: string;输入'{创建:字符串; read: string;读取:字符串; update: string;更新:字符串; delete: string;删除:字符串; }' is not assignable to type 'Actions<"post">'. }' 不可分配给类型 'Actions<"post">'。 Types of property 'create' are incompatible.属性“创建”的类型不兼容。 Type 'string' is not assignable to type '"CREATE_POST"'.类型 'string' 不可分配给类型 '"CREATE_POST"'。

I tried using const assertions ( as const ) on each property value, as well as on the returned object itself, but the property types remain strings.我尝试在每个属性值以及返回的 object 本身上使用 const 断言( as const ),但属性类型仍然是字符串。 Is there any way to do this without just casting the returned object ( as Actions<T> )?有没有什么方法可以做到这一点,而不仅仅是转换返回的 object ( as Actions<T> )? If so, that would kind of defeat the purpose, so I'm hoping there's some way to make the compiler understand.如果是这样,那会破坏目的,所以我希望有某种方法可以让编译器理解。 But I think it might not be able to determine that the runtime toUpperCase call corresponds to the Uppercase transformation in the definition of Actions<T> .但我认为它可能无法确定运行时toUpperCase调用对应于Actions<T>定义中的Uppercase转换。

TS Playground TS游乐场

Edit: Another approach that is close but not quite what I want:编辑:另一种接近但不是我想要的方法:

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");

But in this case the return type for `createCrudActions("post") is:但在这种情况下,`createCrudActions("post") 的返回类型是:

{
  create: `CREATE_${record}`;
  read: `READ_${record}`;
  update: `UPDATE_${record}`;
  delete: `DELETE_${record}`;
}

Whereas I'd like it to be:而我希望它是:

{
  create: `CREATE_POST`;
  read: `READ_POST`;
  update: `UPDATE_POST`;
  delete: `DELETE_POST`;
}

TS Playground TS游乐场

I cajoled this to work with as const on all the properties and casting the toUpperCase() return value as Uppercase<T> ;我劝诱它在所有属性上使用as const并将toUpperCase()返回值转换as Uppercase<T> at that point, though, it's not that much better than as Actions<T> .不过,在这一点上,它并不比as Actions<T>好多少。 Technically this validates the transformation as correct, but the code this protects is unlikely to change and the code that consumes it is equally well-protected from type errors.从技术上讲,这验证了转换是正确的,但是这种保护的代码不太可能改变,并且使用它的代码同样可以很好地防止类型错误。

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,
  };
}

Playground Link 游乐场链接

Do you know why the overload is required in this case, as opposed to just specifying the return type of the function?你知道为什么在这种情况下需要重载,而不是只指定 function 的返回类型吗?

Consider this example:考虑这个例子:

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;

It is clear why we have an error here.很清楚为什么我们在这里有错误。 Because toUpperCase returns string whereas we want to operate on Uppercase<T> .因为toUpperCase返回string ,而我们想对Uppercase<T>进行操作。

But why overloading works in this case?但是为什么在这种情况下重载有效呢? Function overloading acts bivariantly, it means that it compiles if overdload is assignable to function type signature or vice versa. Function 重载具有双变量作用,这意味着如果重载可分配给 function 类型签名,则它会编译,反之亦然。 Of course, we loose type strictness but gain flexibility.当然,我们放松了类型严格性但获得了灵活性。

See this exmaple, without overloading:请参阅此示例,无需重载:


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 is [false, true] . Result[false, true] Since Result has at least one true , function overloading should work.由于Result至少有一个true ,因此 function 重载应该可以工作。

Version with overloading:重载版本:

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");

Try to add extra underscore to Actions utility type:尝试在Actions实用程序类型中添加额外的underscore

type Actions<T extends string> = {
  [K in CrudActions]: `_${Uppercase<K>}_${Uppercase<T>}`;
}

Now, overloading is not assignable to function type signature because non of the types are not assignable to each other.现在,重载不能分配给 function 类型签名,因为没有一个类型不能相互分配。

However, you can move upercasing to a separate function.但是,您可以将大写移动到单独的upercasing In this way you will create only a little piece of unsafe code whereas your main function will be safe .这样,您将只创建一小段不安全的代码,而您的主要 function 将是safe的。 When I say safe I mean: as much as TS allows it to be 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

Now you don't even need Actions type because TS is able to infer all types on its own现在您甚至不需要Actions类型,因为 TS 能够自行推断所有类型

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM