繁体   English   中英

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

[英]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转换。

TS游乐场

编辑:另一种接近但不是我想要的方法:

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

TS游乐场

我劝诱它在所有属性上使用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.

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