TypeScript中的GUID / UUID类型

42
我有这个函数:
function getProduct(id: string){    
    //return some product 
}

id是实际上的GUID。Typescript没有guid类型。是否有可能手动创建类型GUID

function getProduct(id: GUID){    
    //return some product 
}

所以,如果将'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'替换为某个'notGuidbutJustString',那么我将看到TypeScript编译错误。

更新:正如David Sherret所说:无法确保基于正则表达式或其他函数的字符串值在编译时进行检查,但可以在运行时的一个地方执行所有检查。


5
相关问题在Suggestion: Regex-validated string type - Zev Spitz
可能是A typescript Guid class?的重复。 - BuZZ-dEE
1
你可以使用类型别名,但它不会提供任何编译时检查。这只是对开发者的提示。type Guid = string; - Fred
4个回答

29

你可以创建一个字符串的包装器,并将其传递:

class GUID {
    private str: string;

    constructor(str?: string) {
        this.str = str || GUID.getNewGUIDString();
    }

    toString() {
        return this.str;
    }

    private static getNewGUIDString() {
        // your favourite guid generation function could go here
        // ex: https://dev59.com/7HVD5IYBdhLWcg3wDXF3#8809472
        let d = new Date().getTime();
        if (window.performance && typeof window.performance.now === "function") {
            d += performance.now(); //use high-precision timer if available
        }
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
            let r = (d + Math.random() * 16) % 16 | 0;
            d = Math.floor(d/16);
            return (c=='x' ? r : (r & 0x3 | 0x8)).toString(16);
        });
    }
}

function getProduct(id: GUID) {    
    alert(id); // alerts "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
}

const guid = new GUID("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx");
getProduct(guid); // ok
getProduct("notGuidbutJustString"); // errors, good

const guid2 = new GUID();
console.log(guid2.toString()); // some guid string

更新

另一种方法是使用品牌:

type Guid = string & { _guidBrand: undefined };

function makeGuid(text: string): Guid {
  // todo: add some validation and normalization here
  return text as Guid;
}

const someValue = "someString";
const myGuid = makeGuid("ef3c1860-5ce6-47af-a13d-1ed72f65b641");

expectsGuid(someValue); // error, good
expectsGuid(myGuid); // ok, good

function expectsGuid(guid: Guid) {
}

4
但这个包装器将我的问题移至其他地方,因为我仍然可以执行:const guid = new GUID("notGuidbutJustString")。当然,我可以在GUID类中添加一些运行时检查,但我希望在应用程序启动之前就能看到错误... - Rajab Shakirov
5
@Rajab 没有办法在编译时基于正则表达式或其他函数来确保字符串的值。我建议编写单元测试来捕捉这个问题。 - David Sherret
很遗憾,也许将来会有所改变,但无论如何,你的回答都很有帮助。谢谢! - Rajab Shakirov
1
我在GitHub上创建了一个小的Typescript npm包,其中包括单元测试。 - BuZZ-dEE

4

我认为应该在David Sherret的答案上再做一些延伸。
像这样:

// export 
class InvalidUuidError extends Error {
    constructor(m?: string) {
        super(m || "Error: invalid UUID !");

        // Set the prototype explicitly.
        Object.setPrototypeOf(this, InvalidUuidError.prototype);
    }

}


// export 
class UUID 
{
    protected m_str: string;

    constructor(str?: string) {
        this.m_str = str || UUID.newUuid().toString();

        let reg:RegExp = new RegExp("[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}", "i")
        if(!reg.test(this.m_str))
            throw new InvalidUuidError();
    }

    toString() {
        return this.m_str;
    }

    public static newUuid(version?:number) :UUID
    {
        version = version || 4;


        // your favourite guid generation function could go here
        // ex: https://dev59.com/7HVD5IYBdhLWcg3wDXF3#8809472
        let d = new Date().getTime();
        if (window.performance && typeof window.performance.now === "function") {
            d += performance.now(); //use high-precision timer if available
        }
        let uuid:string = ('xxxxxxxx-xxxx-' + version.toString().substr(0,1) + 'xxx-yxxx-xxxxxxxxxxxx').replace(/[xy]/g, (c) => {
            let r = (d + Math.random() * 16) % 16 | 0;
            d = Math.floor(d/16);
            return (c=='x' ? r : (r & 0x3 | 0x8)).toString(16);
        });

        return new UUID(uuid);
    }
}


function getProduct(id: UUID) {    
    alert(id); // alerts "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
}


const guid2 = new UUID();
console.log(guid2.toString()); // some guid string


const guid = new UUID("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx");
getProduct(guid); // ok
getProduct("notGuidbutJustString"); // errors, good

1

我非常喜欢@DavidSherret使用惯用方法更新的版本,用于强类型基元,即通过品牌类型/标记联合类型(+1)。

通过为品牌添加类型参数,甚至可以将ID与特定实体或对象类型(如OP问题中的“产品”)绑定:

type OptionalRecord = Record<string, unknown> | undefined

type Uuid<T extends OptionalRecord = undefined> = string & { __uuidBrand: T }

type Product = {
    id: Uuid<Product>
    name: string
}

type ProductId = Product['id']

function uuid<T extends OptionalRecord = undefined>(value: string) {
    return value as Uuid<T>
}

function productId(value: string) {
    return uuid<Product>(value)
}

function funcWithProductIdArg(productId: ProductId) {
    // do something
    return productId
}

const concreteProductId = productId('123e4567-e89b-12d3-a456-426614174000')

// compiles
funcWithProductIdArg(concreteProductId)

// Argument of type 'string' is not assignable to parameter of type 'ProductId'.
//  Type 'string' is not assignable to type '{ __uuidBrand: Product; }'.(2345)
//
// @ts-expect-error Not a ProductId.
funcWithProductIdArg('123e4567-e89b-12d3-a456-426614174000')

TypeScript Playground


0

在此添加我的答案,该答案基于上面的答案:

// use a brand to create a tagged type. Horrible hack but best we can do
export type UUID = string & { __uuid: void };

// uuid regex
const UUID_REGEX = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;

// type guard to assert a string is a valid uuid
export function isUUID(uuid: string): uuid is UUID {
  return UUID_REGEX.test(uuid);
}

诀窍在于使用 TypeScript类型守卫来断言一个字符串是有效的UUID。

与简单的type UUID = string类型别名不同,TypeScript不会默默地将字符串强制转换为UUID。

在使用期望UUID的位置之前,您需要检查字符串是否为有效的UUID。

以下是一个示例:

function needUUID(uuid: UUID) {
  console.log(uuid)
}

const input = ''

// this won't compile, we don't know whether input is a valid UUID
needUUID(input)

if (isUUID(input) {
  // this compiles successfully, we've verified that input is a valid UUID
  needUUID(input)
} else {
  // this fails to compile, we know input is _not_ a valid uuid
  needUUID(input)
}


简短而甜美,仅进行验证。但是一旦您添加了类型……不妨为其提供构造函数和其他内容(如较早/投票更多的答案中所示……),如果您需要生成UUID而不仅仅是验证一个UUID,则可能仍然有用。 - Daniele Muscetta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接