Typescript在“玩”字符串时返回类型

62lalag4  于 2023-03-31  发布在  TypeScript
关注(0)|答案(2)|浏览(119)

typescript 粉丝!
我有一个函数,它接受一个URL(例如“localhost:3000/common/123”)并返回第一个“/”之后的第一个字符串。

type UserType = "common" | "root" 
export const getUsertypeFromURL = (path: string): UserType => {
    const secondSlashIndex = path.indexOf('/', 1)
    const userType = path.substring(1, secondSlashIndex)
    // typescript error on the following line
    return userType
}

根据定义,我确定getUsertypeFromUrl的输出属于UserType
我想阻止打印脚本显示错误"Type 'string' is not assignable to type 'UserType'"
解决这样的问题的最佳方法是什么?
先谢了

krcsximq

krcsximq1#

注意我已经重命名了这个函数,因为它不接受URL。如果你,未来的你,或者其他人在URL中包含了一个协议,你的逻辑就会崩溃。
关键的一步是使用类型保护(返回 predicate 的函数)。您将以运行时检查保守保护的方式将引用转换为不同的类型。
我已经分享了一些实用程序代码,我经常使用它们来从运行时列表中组合一个保护,我发现这使这个过程更容易。将鼠标悬停在UserType上,你会看到它与类型声明具有相同的效果,但也包含了运行时检查所需的信息。
我保留了你的提取实现,尽管我自己不会写这样的代码--我会写一个基于正则表达式的extractFirstPathElement(url:string)函数,允许例如一个可选的协议在像([a-z]+://)?这样的模式中,因此实际上处理URL。
有了命名函数,你也可以自文档化地从URL字符串中提取第一个路径元素作为副产品。

/** DEFINE TYPES */

const USER_TYPES = ["common", "root"] as const;
type UserType = MemberOf<typeof USER_TYPES>;

/** IMPLEMENT EXTRACTOR */

function extractUserType(resource:string): UserType{
  const secondSlashIndex = resource.indexOf("/", 1);
  const userType = resource.substring(1, secondSlashIndex);
  if (!isMember(USER_TYPES, userType)) {
    throw new Error(`Invalid UserType ${userType} extracted from ${resource}`);
  }
  return userType;
}

/** CASES */

// working 
const example1: UserType = extractUserType(
  "/common/slkjsdf"
);
const example2: UserType = extractUserType("/root/something/something");

console.log(JSON.stringify({ example1, example2 }));

/** non working. Uncomment one at a time and click Run to prove.
 * Each throws an error like `Invalid UserType oot extracted from root/something`
 */

// const erroredExample: UserType = extractUserType("root/something")
// const erroredExample: UserType = extractUserType("something/root")
// const erroredExample: UserType = extractUserType("https://something.com/root/something")

/** UTILITY TYPES AND FUNCTIONS */

function isMember<Arr extends ReadonlyArray<unknown>>(
  arr: Arr,
  candidate: unknown
): candidate is MemberOf<Arr> {
  return arr.includes(candidate);
}

type MemberOf<Array extends readonly unknown[]> = Array[number];
p4rjhz4m

p4rjhz4m2#

cefn的答案是正确的,你不希望那个函数AssertuserTypeUserType类型。这依赖于那个函数的用户给予正确的数据来使函数正常工作,如果你不这样做,将不会有任何错误。
但要做到这一点,需要使用as(再次声明,不要这样做)

return userType as UserType;

这是对您的问题的更具体的解决方案:

const UserType = ["common", "root"] as const;
type UserType = typeof UserType[number];
const isUserType = (value: string): value is UserType => {
    return UserType.includes(value as UserType);
};

export const getUsertypeFromURL = (path: string): UserType => {
    const secondSlashIndex = path.indexOf("/", 1);
    const userType = path.substring(1, secondSlashIndex);
    if (!isUserType(userType)) {
        throw new Error(...);
    }
    return userType;
};

相关问题