从TypeScript类型的两个表示形式,可以是字符串:
A:
{
a: string
b: number
}
B:
{
a: string
}
如何以编程方式测试它们是否兼容,回答“我可以将类型A的变量分配给类型B的值吗?”的问题。 (或相反亦然)。
该功能如下所示:
function match(source: string, target: string): boolean { /** */ }
match('number', 'any') // true
match('any', 'number') // false
match('{a: string; b: number}', '{a: string; b: number}') // true
match('{a: string; b: number}', '{a: string}') // true
match('{a: string}', '{a: string; b: number}') // false
// ...
最简单的方法是什么?
编辑:用例是我有自定义用户生成的类型接口,我想检查它们是否在设计时兼容。 TypeScript只是用于表达这些类型的语法,但这是断言类型匹配的挑战的次要因素。它可以是任何其他类型的系统。
答案 0 :(得分:3)
您可以将编译器作为npm包引入(只需运行npm install typescript
)并在代码中使用编译器。下面的解决方案,只是创建一个小程序来测试两种类型的兼容性。性能可能是一个问题,因此请尝试使用您的真实用例,看看它是否可以接受:
import * as ts from 'typescript'
// Cache declarations (lib.d.ts for example) to improve performance
let sourceFileCache: { [name: string]: ts.SourceFile | undefined } = {};
function match(source: string, target: string): boolean {
let host = ts.createCompilerHost({});
let originalGetSourceFile = host.getSourceFile;
host.getSourceFile = (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => {
// You can add more virtual files, or let the host default read the from the disk
if (fileName === "compatCheck.ts") {
return ts.createSourceFile(fileName, `
type Source = ${source};
type Target = ${target};
let source!: Source;
let target!: Target;
target = source;
`, languageVersion);
}
// Try to get source file from cache, will perfrom better if we reuse the parsed source file.
if (sourceFileCache[fileName] === undefined) {
return sourceFileCache[fileName] = originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
} else {
return sourceFileCache[fileName];
}
}
let program = ts.createProgram(["compatCheck.ts"], {
}, host);
let errors = program.getSemanticDiagnostics();
return !errors.some(e => true);
}
console.log(match('number', 'any')); // true any can be assigned to number
console.log(match('any', 'number')); // true number can be assigned to any as well
console.log(match('{a: string; b: number}', '{a: string; b: number}')); // true
console.log(match('{a: string; b: number}', '{a: string}')); // true
console.log(match('{a: string}', '{a: string; b: number}')); // false
修改
如果您不解析任何默认的lib.d.ts
并且只提供编译器工作所需的最小类型,那么应该性能更好的版本。这是一个非常小的集(Array
,Boolean
,Number
,Function
,IArguments
,Object
,RegExp
和串)。我们也不能包含这些类型中的任何方法,只提供一个简单的定义来保持类型不兼容。如果您需要任何其他类型,则必须明确添加,但这应该足够简单。如果你想允许在这些类型上使用任何方法,你还需要添加这些方法,但我对你的用例的理解是,你只是想比较接口的兼容性,所以这不应该是一个问题:
import * as ts from "typescript";
// Cache declarations (lib.d.ts for example) to improve performance
let sourceFileCache: { [name: string]: ts.SourceFile | undefined } = {};
function match(source: string, target: string): boolean {
let host = ts.createCompilerHost({});
let originalGetSourceFile = host.getSourceFile;
host.directoryExists = ()=> false;
host.fileExists = fileName => fileName === "compatCheck.ts";
host.getSourceFile = (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => {
// You can add more virtual files, or let the host default read the from the disk
if (fileName === "compatCheck.ts") {
return ts.createSourceFile(fileName, `
// Compiler Reuired Types
interface Array<T> { isArray: T & true }
type Boolean = { isBoolean: true }
type Function = { isFunction: true }
type IArguments = { isIArguments: true }
type Number = { isNumber: true }
type Object = { isObject: true }
type RegExp = { isRegExp: true }
type String = { isString: true }
type Source = ${source};
type Target = ${target};
let source!: Source;
let target!: Target;
target = source;
`, languageVersion);
}
// Try to get source file from cache, will perfrom better if we reuse the parsed source file.
if (sourceFileCache[fileName] === undefined) {
return sourceFileCache[fileName] = originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
} else {
return sourceFileCache[fileName];
}
}
let program = ts.createProgram(["compatCheck.ts"], {
noLib: true // Don't parse any default lib, we provide our own types
}, host);
let errors = program.getSemanticDiagnostics()
.concat(program.getDeclarationDiagnostics())
.concat(program.getConfigFileParsingDiagnostics())
.concat(program.getGlobalDiagnostics())
.concat(program.getOptionsDiagnostics())
.concat(program.getSyntacticDiagnostics());
return !errors.some(e => true);
}
console.log(match('number', 'any')); // true any can be assigned to number
console.log(match('any', 'number')); // true number can be assigned to any as well
console.log(match('{a: string; b: number}', '{a: string; b: number}')); // true
console.log(match('{a: string; b: number}', '{a: string}')); // true
console.log(match('{a: string}', '{a: string; b: number}')); // false