【问题标题】:TypeScript: check types compatibilityTypeScript:检查类型兼容性
【发布时间】:2018-07-29 10:29:50
【问题描述】:

来自 TypeScript 类型的两种表示形式,可以是如下字符串:

答:

{
    a: string
    b: number
}

乙:

{
    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 只是用于表达这些类型的语法,但这对于断言类型匹配的挑战来说是次要的。它可以是任何其他类型系统。

【问题讨论】:

  • Typescript 类型和接口在运行时不存在,仅供参考。
  • 正如@matmo 提到的那样,TypeScript 可以防止此类事情的发生,而且是静态的而不是动态的。你的用例是什么。在这种情况下,最好分享您的用例
  • 这个简单的问题不需要任何特定的用例,但在这里:我有自定义的用户生成类型接口,我想检查它们在设计时是否兼容。 TypeScript 只是用于表达这些类型的语法,但这对于断言类型匹配的挑战来说是次要的。它可以是任何其他类型的系统。 @TarunLalwani
  • @Samuel 当你说你想在设计时检查,这是否意味着你想在你的构建步骤中执行这个检查?而不是在运行时?何时调用此兼容性检查以及如何调用?如果这是在构建时在某些节点脚本中完成的,我们可以使用编译器 API 来实现这一点。如果这是在运行时使用编译器,虽然仍然可能会有很大的开销。
  • 我们可以称之为运行时,因为有一个程序正在运行检查用户生成的类型之间的类型兼容性。为此,您将如何在运行时使用编译器?有没有办法提取编译器中进行这种类型兼容性检查的特定部分? @Titian Cernicova-Dragomir

标签: typescript types


【解决方案1】:

您可以将编译器作为 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 并且只提供编译器工作所需的最小类型,则应该执行更好的版本。这是一个非常小的集合(ArrayBooleanNumberFunctionIArgumentsObjectRegExp 和 `String)。我们也不能在这些类型中包含任何方法,而只是提供一个简单的定义来保持类型不兼容。如果您需要任何其他类型,则必须显式添加,但这应该很简单。如果您想允许在这些类型上使用任何方法,您还需要添加这些方法,但我对您的用例的理解,您是否只想比较接口的兼容性,所以这应该不是问题:

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

【讨论】:

  • 您可以使用语言服务的静态缓存实例,并在每次检查新类型时将代码换成单个 SourceFile。这样它就永远不需要重新解析核心库声明。
  • @cspotcode true,这也应该有效,我通过直接缓存SourceFile 来避免重新解析声明,这似乎也有效。我没有使用语言服务,这种方式似乎更容易。
  • 这对于我的用例(恰好是浏览器)来说太慢了。我希望有一种方法可以做到这一点,而无需创建编译器主机。
  • @Samuel,开销是解析和类型检查。其中最大的部分可能是 lib.d.ts 。您可以创建一个最小的lib.d.ts,其中包含您需要的最低限度的内容(可能只是像stringnumberArray 这样的原语,也可能没有方法),这应该会让思考更快。
  • @TitianCernicova-Dragomir 这听起来像是一个有趣的实验,对于我当前的用例来说应该足够了。我怎样才能覆盖lib.d.ts
猜你喜欢
  • 1970-01-01
  • 2019-09-18
  • 1970-01-01
  • 1970-01-01
  • 2018-04-13
  • 2020-01-21
  • 1970-01-01
  • 2021-07-05
  • 2020-06-30
相关资源
最近更新 更多