打字稿中的 GUID / UUID 类型
GUID / UUID type in typescript
我有这个功能:
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 所说:无法在编译时确保基于正则表达式或其他函数的字符串值,但可以在运行 时间一个地方。
您可以围绕字符串创建包装器并将其传递:
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:
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) {
}
我认为应该扩展一下 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:
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
我真的很喜欢@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')
我有这个功能:
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 所说:无法在编译时确保基于正则表达式或其他函数的字符串值,但可以在运行 时间一个地方。
您可以围绕字符串创建包装器并将其传递:
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:
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) {
}
我认为应该扩展一下 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:
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
我真的很喜欢@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')