我正在寻找一种更好的方法来区分程序中不同类型的字符串 - 例如,绝对路径和相对路径.我希望能够让函数接受或返回某种类型的编译器错误,如果我搞砸了.
例如,
function makeAbsolute(path: RelativePath): AbsolutePath {
}
Run Code Online (Sandbox Code Playgroud)
其中AbsolutePath和RelativePath实际上只是字符串.我尝试了类型别名,但实际上并没有创建新类型.接口 -
interface AbsolutePath extends String { }
interface RelativePath extends String { }
Run Code Online (Sandbox Code Playgroud)
但由于这些接口是兼容的,编译器不会阻止我将它们混合起来.如果没有向接口添加属性以使它们不兼容(并且实际上将该属性添加到字符串或者围绕它转换)或使用包装类,我不知道如何做到这一点.还有其他想法吗?
Sea*_*ira 14
有几种方法可以做到这一点.所有这些都涉及使用交叉点"标记"目标类型.
我们可以利用的事实,那就是在一个打字稿名义类型- 该Enum类型来区分,否则同样的结构类型:
枚举类型是Number原始类型的不同子类型
接口和类在结构上进行比较
interface First {}
interface Second {}
var x: First;
var y: Second;
x = y; // Compiles because First and Second are structurally equivalent
Run Code Online (Sandbox Code Playgroud)
枚举根据其"身份"而不同(例如,它们是主格键入的)
const enum First {}
const enum Second {}
var x: First;
var y: Second;
x = y; // Compilation error: Type 'Second' is not assignable to type 'First'.
Run Code Online (Sandbox Code Playgroud)
我们可以利用Enum标称类型来以两种方式之一"标记"或"标记"我们的结构类型:
由于Typescript支持交集类型和类型别名,我们可以使用枚举"标记"任何类型并将其标记为新类型.然后我们可以将基类型的任何实例转换为"标记"类型而不会出现问题:
const enum MyTag {}
type SpecialString = string & MyTag;
var x = 'I am special' as SpecialString;
// The type of x is `string & MyTag`
Run Code Online (Sandbox Code Playgroud)
我们可以使用这种行为将字符串"标记"为存在Relative或Absolute路径(如果我们想要标记a,这将不起作用number- 请参阅第二个选项以了解如何处理这些情况):
declare module Path {
export const enum Relative {}
export const enum Absolute {}
}
type RelativePath = string & Path.Relative;
type AbsolutePath = string & Path.Absolute;
type Path = RelativePath | AbsolutePath
Run Code Online (Sandbox Code Playgroud)
然后我们可以Path通过强制转换将任何字符串实例"标记"为任何类型:
var path = 'thing/here' as Path;
var absolutePath = '/really/rooted' as AbsolutePath;
Run Code Online (Sandbox Code Playgroud)
但是,当我们施放时没有检查到位,所以可以:
var assertedAbsolute = 'really/relative' as AbsolutePath;
// compiles without issue, fails at runtime somewhere else
Run Code Online (Sandbox Code Playgroud)
为了缓解这个问题,我们可以使用基于控制流的类型检查来确保我们只在测试通过时(在运行时)进行转换:
function isRelative(path: String): path is RelativePath {
return path.substr(0, 1) !== '/';
}
function isAbsolute(path: String): path is AbsolutePath {
return !isRelative(path);
}
Run Code Online (Sandbox Code Playgroud)
然后使用它们来确保我们正在处理正确的类型而没有任何运行时错误:
var path = 'thing/here' as Path;
if (isRelative(path)) {
// path's type is now string & Relative
withRelativePath(path);
} else {
// path's type is now string & Absolute
withAbsolutePath(path);
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,我们无法标记number子类型,Weight或者Velocity因为Typescript足够聪明,可以简化number & SomeEnum为number.我们可以使用泛型和字段来"标记"类或接口,并获得类似的名义类型行为.这类似于@JohnWhite用他的私人名字所建议的,但只要泛型是enum:
/**
* Nominal typing for any TypeScript interface or class.
*
* If T is an enum type, any type which includes this interface
* will only match other types that are tagged with the same
* enum type.
*/
interface Nominal<T> { 'nominal structural brand': T }
// Alternatively, you can use an abstract class
// If you make the type argument `T extends string`
// instead of `T /* must be enum */`
// then you can avoid the need for enums, at the cost of
// collisions if you choose the same string as someone else
abstract class As<T extends string> {
private _nominativeBrand: T;
}
declare module Path {
export const enum Relative {}
export const enum Absolute {}
}
type BasePath<T> = Nominal<T> & string
type RelativePath = BasePath<Path.Relative>
type AbsolutePath = BasePath<Path.Absolute>
type Path = RelativePath | AbsolutePath
// Mark that this string is a Path of some kind
// (The alternative is to use
// var path = 'thing/here' as Path
// which is all this function does).
function toPath(path: string): Path {
return path as Path;
}
Run Code Online (Sandbox Code Playgroud)
我们必须使用我们的"构造函数"从基类型创建我们的"品牌"类型的实例:
var path = toPath('thing/here');
// or a type cast will also do the trick
var path = 'thing/here' as Path
Run Code Online (Sandbox Code Playgroud)
同样,我们可以使用基于控制流的类型和函数来提高编译时的安全性:
if (isRelative(path)) {
withRelativePath(path);
} else {
withAbsolutePath(path);
}
Run Code Online (Sandbox Code Playgroud)
并且,作为额外的奖励,这也适用于number子类型:
declare module Dates {
export const enum Year {}
export const enum Month {}
export const enum Day {}
}
type DatePart<T> = Nominal<T> & number
type Year = DatePart<Dates.Year>
type Month = DatePart<Dates.Month>
type Day = DatePart<Dates.Day>
var ageInYears = 30 as Year;
var ageInDays: Day;
ageInDays = ageInYears;
// Compilation error:
// Type 'Nominal<Month> & number' is not assignable to type 'Nominal<Year> & number'.
Run Code Online (Sandbox Code Playgroud)
改编自https://github.com/Microsoft/TypeScript/issues/185#issuecomment-125988288
abstract class RelativePath extends String {
public static createFromString(url: string): RelativePath {
// validate if 'url' is indeed a relative path
// for example, if it does not begin with '/'
// ...
return url as any;
}
private __relativePathFlag;
}
abstract class AbsolutePath extends String {
public static createFromString(url: string): AbsolutePath {
// validate if 'url' is indeed an absolute path
// for example, if it begins with '/'
// ...
return url as any;
}
private __absolutePathFlag;
}
Run Code Online (Sandbox Code Playgroud)
var path1 = RelativePath.createFromString("relative/path");
var path2 = AbsolutePath.createFromString("/absolute/path");
// Compile error: type 'AbsolutePath' is not assignable to type 'RelativePath'
path1 = path2;
console.log(typeof path1); // "string"
console.log(typeof path2); // "string"
console.log(path1.toUpperCase()); // "RELATIVE/PATH"
Run Code Online (Sandbox Code Playgroud)
在你写一本关于它的书的每一个层面上都是错的...... - 但它确实很好用,它确实完成了工作.
自创建以来被控制为这样的,AbsolutePath和RelativePath实例是:
StringTS编译器(继承自),允许调用字符串函数这类似于"伪造的继承"(因为TS编译器被告知继承,但在运行时不存在继承)以及额外的数据验证.由于没有添加任何公共成员或方法,因此不应该导致意外的运行时行为,因为在编译和运行时期间都存在相同的假设功能.
| 归档时间: |
|
| 查看次数: |
1501 次 |
| 最近记录: |