TypeScript 2.x 学习指南(六)

Claude Code 完整代码分析报告 阅读详情

原文:zh.annas-archive.org/md5/30977cdd12d2fd68062b20bc58beee42

译者:飞龙

协议:CC BY-NC-SA 4.0

第十五章:使用 TypeScript 编译器和语言服务

在本章中,我们将学习 TypeScript 编译器和 TypeScript 语言服务的内部机制。

这些主题可能看起来非常高级,并不是每个人都觉得有用。虽然它确实是高级用户的话题,但事实是,每个人都可以从理解 TypeScript 语言服务中受益。理解 TypeScript 编译器的编译器 API 可以帮助我们开发许多种类的开发工具,并自动化我们开发工作流程的某些方面。

本章的目标不是让你成为编译器内部或 TypeScript 工具开发的专家,而是温和地引导你了解这个非常广泛的主题。在本章中,我们将涵盖以下内容:

  • TypeScript 编译器的内部架构

  • 使用编译器 API 进行编程

  • 使用 ts-simple-ast 进行操作

  • 实现自定义代码分析工具

TypeScript 编译器的内部架构

在本节中,我们将学习 TypeScript 编译器中的主要组件。我们将学习每个组件的主要职责,以及它们的预期输入和输出。

以下图表描述了 TypeScript 架构的主要组件:

https://github.com/OpenDocCN/freelearn-html-css-js-zh/raw/master/docs/lrn-ts-2x/img/3e3e592e-d2b7-4083-8613-dbd668e1dfd4.png

TypeScript 架构的组成部分

TypeScript 核心 API 是所有事物的基石,由扫描器、解析器、绑定器、类型检查器和发射器等元素组成。

语言服务和独立编译器(tsc 命令行工具)位于核心编译器 API 之上。最后,Visual Studio 适配器和 TypeScript 独立服务器(tsserver)被设计用来促进 TypeScript 与 Visual Studio 和其他源代码编辑器的集成。

TypeScript 的官方文档将 TypeScript 独立服务器定义为以下内容:

“TypeScript 独立服务器(又称 tsserver)是一个封装 TypeScript 编译器和语言服务并通过 JSON 协议暴露它们的 Node 可执行文件。tsserver 非常适合编辑器和 IDE 支持。”

扫描器

扫描器将源代码文件转换成标记流。扫描器在其他关于编译器的资源中也被称为词法分析器。扫描器被解析器使用。

词素和标记

词素是源程序中匹配标记模式的字符序列。我们可以这样说,标记有一个模式,而在某些情况下,模式可以由许多词素匹配。因此,在编程语言中,存在无限多的潜在词素和有限的标记数量。

理解词素和标记之间的区别的最简单方法是通过查看以下代码片段的例子:

while (y >= t) y = y - 3; 

以下代码片段将被解析成以下词素和标记:

词素标记
whileWhileKeyword
(OpenParenToken
yIdentifier
>=GreaterThanEqualsToken
tIdentifier
)CloseParenToken
yIdentifier
=EqualsToken
yIdentifier
-MinusToken
3NumericLiteral
;SemicolonToken
EndOfFileToken

在 TypeScript 中,标记在SyntaxKind枚举中定义:

export const enum SyntaxKind { 
    Unknown, 
    EndOfFileToken, 
    SingleLineCommentTrivia, 
    MultiLineCommentTrivia, 
    NewLineTrivia, 
    WhitespaceTrivia, 
    ShebangTrivia, 
    ConflictMarkerTrivia, 
    NumericLiteral, 
    StringLiteral, 
    JsxText, 
    //... 

SyntaxtKind枚举在 TypeScript 源代码的/src/compiler/types.ts文件中定义。

如果您想探索 TypeScript 项目的整个源代码,请参阅 GitHub 上的官方 TypeScript 仓库github.com/Microsoft/TypeScript。

解析器

TypeScript 解析器使用扫描器遍历我们的源代码文件并将它们转换成标记流。

TypeScript 解析器然后将标记流转换成一个称为抽象语法树(AST)的树状数据结构。这个树状数据结构中的每个元素都称为节点。节点是 AST 的基本构建块。

AST

抽象语法树(AST)是由解析器创建的树状数据结构。这种数据结构允许 TypeScript 编译器遍历我们的源代码以执行许多核心任务,例如生成输出 JavaScript 代码。我们将在本章后面了解更多关于 AST 的内容。

符号

Basarat Ali Syed 编写的 TypeScript 教科书将符号描述如下:

“符号将 AST 中的声明节点与其他贡献同一实体的声明连接起来。符号是语义系统的基本构建块。”

符号类在 TypeScript 源代码中定义如下:

function Symbol(this: Symbol, flags: SymbolFlags, name: __String) { 
    this.flags = flags; 
    this.escapedName = name; 
    this.declarations = undefined; 
    this.valueDeclaration = undefined; 
    this.id = undefined; 
    this.mergeId = undefined; 
    this.parent = undefined; 
}

一个符号包含对类型声明的引用和一些帮助我们识别其某些特性的标志。

绑定器

Basarat Ali Syed 编写的 TypeScript 教科书将绑定器描述如下:

“绑定器用于将源代码的各个部分连接成一个连贯的类型系统,然后该系统可以被检查器使用。绑定器的主要责任是创建符号。”

TypeScript 支持一个称为声明合并的功能,允许我们将使用相同名称声明的两个单独的声明合并为一个单一的定义。例如,以下代码片段声明了两个名为Person的接口和一个名为person的变量:

interface Person { 
    name: string; 
} 

interface Person { 
    surname: string; 
} 

const person: Person = { name: "Remo", surname: "Jansen" }; 

变量的类型是Person,正如我们所见,该类型包含在先前声明的接口中声明的属性。这是因为声明合并机制将这两个声明合并为一个唯一的类型。这与绑定器的行为直接相关。

类型检查器

类型检查器可能是 TypeScript 编译器中最重要的组件。类型检查器使用抽象语法树(每个文件一个)和符号作为输入,并负责监督源代码中类型错误的识别。

发射器

发射器是负责生成输出代码的组件。输出通常是遵循支持的规范之一(ES3、ES5 或 ES6)的 JavaScript,但它也可以是类型定义或源映射文件。

语言服务

TypeScript 编译器包括一个专门设计来为开发者提供良好开发体验的附加组件。以下段落是从官方 TypeScript 文档中提取的:

“语言服务”在核心编译器管道周围提供了一个额外的层,非常适合类似编辑器的应用程序。语言服务支持典型的编辑器操作集,如语句完成、签名帮助、代码格式化和大纲、着色等。基本的重构,如重命名,调试接口辅助,如验证断点,以及 TypeScript 特定的功能,如支持增量编译(命令行上的–watch 等效)。语言服务旨在高效地处理在长期编译上下文中随时间变化的文件场景;从这个意义上说,语言服务从其他编译器接口的角度来看,对与程序和源文件的工作提供了略微不同的视角。”

理解抽象语法树(AST)

正如我们已经学到的,抽象语法树(AST)是一种树形数据结构,用于表示用编程语言编写的源代码的抽象句法结构。AST 的每个节点都代表源代码中发生的一个构造。

现在我们将查看一个小型的 TypeScript 代码片段,以详细了解 AST。以下代码片段没有什么特别之处——它只是声明了一个名为Weapon的接口和几个类,名为Katana和Ninja。然后它创建了一个Ninja类的实例并调用了它的一种方法:

interface Weapon { 
    tryHit(fromDistance: number): boolean; 
} 

class Katana implements Weapon { 
    public tryHit(fromDistance: number) { 
        return fromDistance <= 2; 
    } 
} 

class Ninja { 
    private _weapon: Weapon; 
    public constructor(weapon: Weapon) { 
        this._weapon = weapon; 
    } 
    public fight(fromDistance: number) { 
        return this._weapon.tryHit(fromDistance); 
    } 
} 

const ninja = new Ninja(new Katana()); 
ninja.fight("5"); 

TypeScript 编译器生成的先前代码片段的 AST 如下所示:

https://github.com/OpenDocCN/freelearn-html-css-js-zh/raw/master/docs/lrn-ts-2x/img/8adc6f41-2ece-476d-a243-f9c76f3ada99.png

为了便于理解,已经删除了先前 AST 的一些节点。我们可以看到 AST 从SourceFile节点开始,以EndOfFileToken节点结束。在这两个节点之间,我们有一个接口声明节点(InterfaceDeclaration),两个类声明节点(ClassDeclaration),一个变量声明节点(VariableStatement),以及最终的方法调用节点(ExpressionStatement)。

我们现在将关注这些节点中的一个:表示接口声明的节点(InterfaceDeclaration)。正如我们之前看到的,接口声明看起来如下:

interface Weapon { 
    tryHit(fromDistance: number): boolean; 
} 

前面代码片段的 AST 中的 InterfaceDeclaration 节点如下所示:

https://github.com/OpenDocCN/freelearn-html-css-js-zh/raw/master/docs/lrn-ts-2x/img/9e8467e2-a31c-4eb8-a0b8-c0a7a6629136.png

在前面 AST 的表示中,我们可以看到组成接口声明 AST 且具有唯一方法签名的每个节点的名称。例如,我们可以看到声明从表示 interface 关键字的节点(InterfaceKeyword)开始,并且其后是接口的名称(Identifier)。我们还可以看到 tryHit 方法接受一个数字(NumberKeyword)作为参数,并返回一个布尔值(BooleanKeyword)。

AST 节点具有某些属性。例如,前面示例中的 NumberKeyword 具有以下属性:

NumberKeyword 
pos:43 
start:44 
end:50 
flags:0 
kind:133 

这些属性使我们能够识别节点的类型(kind 属性)及其在源代码中的位置(pos 和 end 属性)。kind 是对 Token 的引用。值 133 是 SyntaxKind 枚举中 NumberKeyword 属性的值。

现在我们已经知道了 TypeScript AST 是什么以及它的样子。在接下来的部分,我们将学习一个可以帮助我们可视化 AST 的工具。

TypeScript AST 查看器

TypeScript AST 查看器是一个开源应用程序,允许我们探索给定 TypeScript 代码片段的 AST。此应用程序可在 ts-ast-viewer.com 上在线使用:

https://github.com/OpenDocCN/freelearn-html-css-js-zh/raw/master/docs/lrn-ts-2x/img/911252a4-64b9-4ba0-94a6-c81040af7333.png

示例应用程序

伴随源代码包括一个非常小的应用程序,我们将在本章的其余部分使用它。以下小节描述了示例应用程序中的每个组件。

interfaces.ts

interfaces.ts 文件声明并导出了一些接口:

export interface Weapon { 
    tryHit(fromDistance: number): boolean; 
} 

export interface Named { 
    name: string; 
} 

katana.ts

katana.ts 文件声明了一个名为 BaseWeapon 的基类和一个名为 Katana 的派生类:

import { Weapon, Named } from "./interfaces"; 

export class BaseWeapon { 
    damage = 25; 
} 

export class Katana extends BaseWeapon implements Weapon, Named  { 
    name = "Katana"; 
    public tryHit(fromDistance: number) { 
        return fromDistance <= 2; 
    } 
} 

ninja.ts

ninja.ts 文件声明了一个名为 Ninja 的类:

import { Weapon } from "./interfaces"; 

export class Ninja { 
    private _weapon: Weapon; 
    public constructor(weapon: Weapon) { 
        this._weapon = weapon; 
    } 
    public fight(fromDistance: number) { 
        return this._weapon.tryHit(fromDistance); 
    } 
} 

main.ts

main.ts 文件是应用程序的入口点。它创建了一个 Katana 实例和一个 Ninja 实例,然后调用 Ninja 实例的一个方法:

import { Ninja } from "./ninja"; 
import { Katana } from "./katana"; 

const ninja = new Ninja(new Katana()); 

ninja.fight(5); 

broken.ts

伴随源代码还包括一个名为 broken.ts 的文件。此文件故意包含一些编译错误,因为它用于演示如何执行错误诊断:

import { Ninja } from "./ninja"; 
import { Katana } from "./katana"; 

const ninja = new Ninja(new Katana()); 

ninja.fight("5"); 

遍历 TypeScript AST

我们已经学习了如何使用在线 TypeScript AST 查看器可视化 TypeScript AST。在这个时候,我们自然会问自己所有这些信息是从哪里来的。在本节中,我们将演示如何使用 TypeScript 编译器 API 访问 AST。

在创建 package.json 文件并使用 npm 安装 TypeScript 之后,我们首先需要做的是创建一个新的 TypeScript 文件并将 TypeScript 作为模块导入:

import * as ts from "typescript"; 

然后我们需要使用对象字面量声明 TypeScript 编译器的配置:

const options = { 
    module: ts.ModuleKind.CommonJS, 
    target: ts.ScriptTarget.ES5, 
}; 

接下来,我们需要创建一个新的程序:

const program = ts.createProgram( 
    [ 
        "./app/interfaces.ts", 
        "./app/ninja.ts", 
        "./app/katana.ts", 
        "./app/main.ts" 
    ], 
    options 
); 

程序是一组源文件和一组表示编译单元的编译选项的集合。程序是类型系统和代码生成系统的主入口点。

然后我们需要创建 TypeScript 类型检查器的实例:

const checker = program.getTypeChecker(); 

到目前为止,我们可以编写一些代码来遍历程序中的源文件。以下代码片段遍历给定源文件的 AST,并返回每个文件中声明的类和接口列表。

它使用程序实例的 getSourceFiles 方法来访问程序中的源文件。lib.d.ts 文件和 node_modules 目录下的文件将被忽略。

代码片段使用名为 visit 的递归函数来遍历 AST 中的节点。递归函数将每个节点与我们要查找的标记(ClassDeclaration 和 InterfaceDeclaration)进行比较,以识别类和接口:

interface Result { 
    fileName: string; 
    classes: string[]; 
    interfaces: string[]; 
} 

const entities = program.getSourceFiles().map(file => { 

    if ( 
        file.fileName.indexOf("lib.d.ts") !== -1 || 
        file.fileName.indexOf("node_modules") !== -1 
    ) { 
        return null; 
    } 

    const result = { 
        fileName: file.fileName, 
        classes: [] as string[], 
        interfaces: [] as string[] 
    }; 

    const visit = (node: ts.Node) => { 
        if (node.kind === ts.SyntaxKind.ClassDeclaration) { 
            // Find class identifier 
            node.getChildren().forEach(n => { 
                if (n.kind === ts.SyntaxKind.Identifier) { 
                    const name = (n as ts.Identifier).getFullText(); 
                    result.classes.push(name); 
                } 
            }); 
        } else if (node.kind === ts.SyntaxKind.InterfaceDeclaration) { 
            // Find interface identifier 
            node.getChildren().forEach(n => { 
                if (n.kind === ts.SyntaxKind.Identifier) { 
                    const name = (n as ts.Identifier).getFullText(); 
                    result.interfaces.push(name); 
                } 
            }); 
        } else if (node.kind === ts.SyntaxKind.ModuleDeclaration) { 
            // Iterate module nodes 
            ts.forEachChild(node, visit); 
        } 
    }; 

    ts.forEachChild(file, visit); 
    return result; 

}).filter(e => e !== null) as Result[]; 

如果找到的标记是模块声明,我们将再次调用递归函数 visit。一旦我们成功找到源代码中的所有类和接口,我们可以使用简单的 forEach 循环在控制台中显示它们:

entities.forEach(e => { 
    console.log(chalk.cyan(` 
        FILE: ${e.fileName}n 
        CLASSES: ${e.classes.length > 0 ? e.classes : "N/A"}n 
        INTERFACES: ${e.interfaces.length > 0 ? e.interfaces : "N/A"}n 
    `)); 
}); 

注意,chalk 模块可以通过 npm 安装,并用于在控制台输出中显示彩色文本。

现在我们知道了如何访问和遍历 TypeScript AST。正如我们所见,创建此类任务所需的过程相当繁琐。然而,有一个开源工具可以帮助我们轻松地遍历 TypeScript AST:ts-simple-ast。

使用 ts-simple-ast

正如我们在上一节中学到的,当涉及到与 TypeScript 一起工作时,AST 并不是很复杂。然而,有一个名为 ts-simple-ast 的开源 npm 模块可以使处理 TypeScript AST 更加容易!在本节中,我们将查看多个示例,以了解如何使用 ts-simple-ast。

使用 ts-simple-ast 遍历 AST

以下代码片段实现了一个几乎与上一节中实现的程序完全相同的应用程序。最显著的区别是,我们不是使用核心 TypeScript 编译器 API,而是将使用 ts-simple-ast 辅助工具:

import chalk from "chalk"; 
import Ast, { DiagnosticMessageChain } from "ts-simple-ast"; 

以下函数在本章的许多示例中使用,用于根据某些文件和所需的编译器设置获取 TypeScript AST 的实例:

function getAst(tsConfigPath: string, sourceFilesPath: string) { 
  const ast = new Ast({ 
    tsConfigFilePath: tsConfigPath, 
    addFilesFromTsConfig: false 
  }); 
  ast.addExistingSourceFiles(sourceFilesPath); 
  return ast; 
} 

我们可以使用 getAst 函数来访问 ts-simple-ast AST,然后使用 getSourceFiles 方法来访问程序中的源文件:

const myAst = getAst("./tsconfig.json", "./app/*.ts"); 
const files = myAst.getSourceFiles(); 

到目前为止,我们可以使用 getFilePath 来获取源文件的路径,以及使用 getClasses 和 getInterfaces 方法来访问源文件中的类和接口声明:

const entities = files.map(f => { 
  return { 
    fileName: f.getFilePath(), 
    classes: f.getClasses().map(c => c.getName()), 
    interfaces: f.getInterfaces().map(i => i.getName()) 
  }; 
}); 

如我们所见,ts-simple-ast 辅助工具可以极大地简化遍历 TypeScript AST 或搜索特定类型的实体。

最后,我们可以在命令行界面显示类的名称:

entities.forEach(e => { 
  console.log( 
    chalk.cyan(` 
        FILE: ${e.fileName}n 
        CLASSES: ${e.classes.length > 0 ? e.classes : "N/A"}n 
        INTERFACES: ${e.interfaces.length > 0 ? e.interfaces : "N/A"}n 
    `) 
  ); 
}); 

使用 ts-simple-ast 进行诊断

以下代码片段实现了一个非常小的应用程序,该应用程序使用 ts-simple-ast 通过错误诊断 API 在 TypeScript 文件中查找错误。该应用程序使用 chalk npm 模块在命令行界面以红色字体显示错误:

import chalk from "chalk"; 
import Ast, { DiagnosticMessageChain } from "ts-simple-ast"; 

以下函数是我们在前面部分使用的相同的 getAst 函数:

function getAst(tsConfigPath: string, sourceFilesPath: string) { 
  const ast = new Ast({ 
    tsConfigFilePath: tsConfigPath, 
    addFilesFromTsConfig: false 
  }); 
  ast.addExistingSourceFiles(sourceFilesPath); 
  return ast; 
} 

ts-simple-ast 提供的 AST 包含一个名为 getDiagnostics 的方法,它允许我们访问检测到的编译错误。getErrors 函数展示了如何遍历每个诊断以及如何访问底层的 DiagnosticMessageChain。

方法 diagnostic.getMessageText 返回一个字符串或一个 DiagnosticMessageChain。DiagnosticMessageChain 实现了迭代器模式,这就是为什么我们使用 DiagnosticMessageChain.getNext 方法的原因:

function getErrors(ast: Ast) { 

  const diagnostics = ast.getDiagnostics(); 

  function dmcToString(dmc: DiagnosticMessageChain, msg: string = ""): string { 
    const messageText = dmc.getMessageText(); 
    const code = dmc.getCode(); 
    msg += `${code} ${messageText}n`; 
    const next = dmc.getNext(); 
    return next ? dmcToString(next, msg) : msg; 
  } 

  const errors = diagnostics.map(diagnostic => { 
    const code = diagnostic.getCode(); 
    const sourceOrUndefined = diagnostic.getSourceFile(); 
    const source = sourceOrUndefined ? sourceOrUndefined.getFilePath() : ""; 
    const line = sourceOrUndefined 
      ? sourceOrUndefined.getLineNumberFromPos(diagnostic.getStart() || 0) 
      : ""; 
    const stringOrDMC = diagnostic.getMessageText(); 
    const messageText = 
      typeof stringOrDMC === "string" ? stringOrDMC : dmcToString(stringOrDMC); 
    return ` 
            ERROR CODE: ${code} 
            DESCRIPTION: ${messageText} 
            FILE: ${source} 
            LINE: ${line} 
        `; 
  }); 

  return errors; 
} 

const myAst = getAst("./tsconfig.json", "./app/broken.ts"); 

getErrors(myAst).forEach(err => console.log(chalk.red(err))); 

使用 ts-simple-ast 访问类详情

以下代码片段演示了我们可以如何使用 ts-simple-ast API 来访问和操作类声明。

注意,前面的示例不是用来执行的。它展示了 ts-simple-ast API 中可用的方法,但它不是一个可执行的演示。

就像前面的示例一样,我们将使用 getAst、getSourceFiles 和 getClasses 方法来找到我们源代码中的所有类声明。

然后,我们将使用一些工具来访问类声明的详细信息,包括方法、派生类和属性等。我们还将演示如何通过添加基类或新方法等操作来修改类声明:

import chalk from "chalk"; 
import Ast, { DiagnosticMessageChain } from "ts-simple-ast"; 

function getAst(tsConfigPath: string, sourceFilesPath: string) { 
    const ast = new Ast({ 
      tsConfigFilePath: tsConfigPath, 
      addFilesFromTsConfig: false 
    }); 
    ast.addExistingSourceFiles(sourceFilesPath); 
    return ast; 
} 

const myAst = getAst("./tsconfig.json", "./app/*.ts"); 
const files = myAst.getSourceFiles(); 

files.forEach(file => { 

    // Find all classes 
    const classes = file.getClasses(); 

    // Find class by name 
    const class1 = file.getClass("Katana"); 

    // Find class with no constructors 
    const firstClassWithConstructor = file.getClass( 
        c => c.getConstructors().length > 0 
    ); 

    // Add a class 
    const classDeclaration = file.addClass({ 
        name: "ClassName" 
    }); 

    // Get extends 
    const extendsExpression = classDeclaration.getExtends(); 

    // Set extends 
    classDeclaration.setExtends("BaseClass"); 

    // Remove extends 
    classDeclaration.removeExtends(); 

    // Get derived classes 
    const derivedClasses = classDeclaration.getDerivedClasses(); 

    // Remove one class 
    if (classDeclaration) { 
        classDeclaration.remove(); 
    } 

    // Get instance methods 
    const instanceMethods = classDeclaration.getInstanceMethods(); 

    // Get static methods 
    const staticMethods = classDeclaration.getStaticMethods(); 

    // Add method 
    const method = classDeclaration.addMethod( 
        { isStatic: true, name: "myMethod", returnType: "string" } 
    ); 

    // Remove method 
    method.remove(); 

    // Get instance properties 
    const instanceProperties = classDeclaration.getInstanceProperties(); 

    // Get static properties 
    const staticProperties = classDeclaration.getStaticProperties(); 

    // Add a property 
    const property = classDeclaration.addProperty({ isStatic: true, name: "prop", type: "string" }); 

    // Remove property 
    property.remove(); 

}); 

使用 ts-simple-ast 访问模块详情

以下代码片段演示了我们可以如何使用 ts-simple-ast API 来访问和操作模块 import 和 export 声明。

注意,前面的示例不是用来执行的。它展示了 ts-simple-ast API 中可用的方法,但它不是一个可执行的演示。

就像前面的示例一样,我们将使用 getAst 和 getSourceFiles 方法来访问源代码的对象:

import chalk from "chalk"; 
import Ast, { DiagnosticMessageChain } from "ts-simple-ast"; 

function getAst(tsConfigPath: string, sourceFilesPath: string) { 
    const ast = new Ast({ 
      tsConfigFilePath: tsConfigPath, 
      addFilesFromTsConfig: false 
    }); 
    ast.addExistingSourceFiles(sourceFilesPath); 
    return ast; 
} 

const myAst = getAst("./tsconfig.json", "./app/*.ts"); 
const files = myAst.getSourceFiles(); 

然后,我们将使用一些方法来访问模块 import 和 export 声明的详细信息。我们还演示了如何通过添加默认的 export 等操作来添加和删除模块 import 和 export 声明:

files.forEach(file => { 

  const functionDeclaration = file.getFunction("someFunction"); 

  if (functionDeclaration) { 

    // Is exported 
    functionDeclaration.isExported(); 
    functionDeclaration.isNamedExport(); 
    functionDeclaration.isDefaultExport(); 

    // Has export keyword 
    functionDeclaration.hasExportKeyword(); 
    functionDeclaration.hasDefaultKeyword(); 

    // Access export keywords 
    functionDeclaration.getExportKeyword(); 
    functionDeclaration.getDefaultKeyword(); 

    // Set is export 
    functionDeclaration.setIsDefaultExport(true);  
    functionDeclaration.setIsDefaultExport(false); 

    // Set is exported 
    functionDeclaration.setIsExported(true);  
    functionDeclaration.setIsExported(false); 

  } 

  // Get all imports 
  const imports = file.getImportDeclarations(); 

  // Add import 
  const importDeclaration = file.addImportDeclaration({ 
      defaultImport: "MyClass", 
      moduleSpecifier: "./file" 
  }); 

  // Remove import 
  importDeclaration.remove(); 

  // Get default import 
  const defaultImport = importDeclaration.getDefaultImport(); 

  // Get named imports 
  const namedImports = importDeclaration.getNamedImports(); 

  // Add named import 
  const namedImport = importDeclaration.addNamedImport({ 
      name: "MyClass", 
      alias: "MyAliasName" // alias is optional 
  }); 

  // Remove named import 
  namedImport.remove(); 

}); 

访问语言服务 API

语言服务 API 建立在核心编译器 API 之上,它被设计用来为软件工程师提供独立于他们选择的 IDE 或代码编辑器的优秀开发体验。

我们将使用ts-simple-ast来访问语言服务 API。我们可以在 AST 实例中使用getLanguageService方法来访问语言服务 API:

myAst.getLanguageService(); 

语言服务 API 实现了允许我们执行常见编辑任务的方法,例如重命名变量或自动实现接口。以下截图显示了语言服务 API 中的一些可用方法:

https://github.com/OpenDocCN/freelearn-html-css-js-zh/raw/master/docs/lrn-ts-2x/img/7c1f8e17-a5e4-4f43-8214-350fed0df449.png

现在,我们将创建一个非常小的应用程序,该应用程序使用语言服务 API 在配套源代码中包含的示例应用程序中查找接口。该应用程序将显示每个接口及其实现的名称:

import chalk from "chalk"; 
import { flatten, join } from "lodash"; 
import Ast, { DiagnosticMessageChain } from "ts-simple-ast"; 
import * as ts from "typescript"; 

function getAst(tsConfigPath: string, sourceFilesPath: string) { 
    const ast = new Ast({ 
      tsConfigFilePath: tsConfigPath, 
      addFilesFromTsConfig: false 
    }); 
    ast.addExistingSourceFiles(sourceFilesPath); 
    return ast; 
} 

const myAst = getAst("./tsconfig.json", "./app/*.ts"); 
const languageService = myAst.getLanguageService(); 
const files = myAst.getSourceFiles(); 
const interfaceDeclarations = flatten(files.map(f => f.getInterfaces())); 

然后,我们需要使用getName方法找到每个接口声明的名称。

我们也将尝试使用getImplementations方法找到它们的每个实现,这个方法是语言服务 API 的一部分。该方法期望我们传递声明接口的节点。节点可以通过interfaceDeclaration.getNameNode方法访问。

一旦我们找到了实现,我们需要找到它们的名称。我们通过搜索称为Identifier类型的节点的名称来完成此操作:

const result = interfaceDeclarations.map(interfaceDeclaration => { 

    const interfaceName = interfaceDeclaration.getName(); 

    const implementations = languageService.getImplementations( 
        interfaceDeclaration.getNameNode() 
    ); 

    const implementationNames = implementations.map(implementation => { 
        const children = implementation.getNode().getChildren(); 
        const identifier = children.filter( 
            child => child.getKind() === ts.SyntaxKind.Identifier 
        )[0]; 
        const implementationName = identifier.getText(); 
        return implementationName; 
    }); 

    return { 
        interface: interfaceName, 
        implementations: implementationNames 
    }; 

}); 

最后,我们在控制台中显示结果:

console.log( 
    result.forEach( 
        o => console.log( 
            `- ${o.interface} is implemented by ${join(o.implementations, ",")}` 
        ) 
    ) 
); 

如果我们执行应用程序,我们应该能够在我们的控制台中看到以下内容显示:

- Weapon is implemented by Katana 
- Named is implemented by Katana 

自从 TypeScript 2.2 以来,可以编写语言服务插件来扩展语言服务 API。自定义语言服务可以帮助开发者获得更好的开发体验。例如,有一个语言服务在开发者处理 GraphQL 查询时提供自动完成和错误诊断功能。在此之前,GraphQL 查询只是文本,因此它们在实现上有些繁琐。实现我们的语言服务插件超出了本书的范围,但如果你希望了解更多,你可以在github.com/Microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin上学习。

实现一个 yUML 编译器

在本节中,我们将把在本章中学到的所有内容整合起来,创建一个自定义的开发工具。我们将编写一个工具,它接受 TypeScript 源代码作为输入,并生成一个统一建模语言(UML)类图。类图通过显示系统的类、它们的属性、方法和对象之间的关系来描述系统的结构。

类图看起来如下:

https://github.com/OpenDocCN/freelearn-html-css-js-zh/raw/master/docs/lrn-ts-2x/img/794d7e60-33ed-4437-af11-ae46afdd8b1c.png

我们将使用 TypeScript 编译器 API 和 ts-simple-ast 来遍历由配套源代码中包含的示例应用程序生成的 AST。然后,我们将以一个称为 yUML 的领域特定语言(DSL)的形式生成一些代码。最后,我们将 yUML DSL 发布到在线服务以生成图像。我们将从 TypeScript 转换为 yUML,这意味着我们可以将此视为创建一个 yUML 编译器。

我们将首先导入一些必需的模块:

import * as fs from "fs"; 
import { flatten, join } from "lodash"; 
import * as path from "path"; 
import * as request from "request"; 
import Ast, * as SimpleAST from "ts-simple-ast"; 
import * as ts from "typescript"; 

fs 和 path 模块是 Node.js 的原生核心模块,不需要安装。但是,我们需要 Node.js 的类型定义(@types/node)。

注意,flatten 函数是 lodash npm 模块的一部分。此函数允许我们将多维数组(例如,数组数组)转换为只有一个维度的数组。

然后,我们将声明两个接口,用于表示类、方法或属性的详细信息。在这个例子中,我们只将使用方法或属性的名称:

interface MethodDetails { 
  name: string; 
} 

interface PropertyDetails { 
  name: string; 
} 

以下代码片段声明了一个名为 templates 的常量变量。模板是接受名称并返回包含 yUML DSL 片段的字符串的函数:

const templates = { 
  url: (dsl: string) => `http://yuml.me/diagram/scruffy/class/${dsl}`, 
  composition: "+->", 
  implementsOrExtends: (abstraction: string, implementation: string) => { 
    return ( 
      `${templates.plainClassOrInterface(abstraction)}` + 
      `^-${templates.plainClassOrInterface(implementation)}` 
    ); 
  }, 
  plainClassOrInterface: (name: string) => `[${name}]`, 
  colorClass: (name: string) => `[${name}{bg:skyblue}]`, 
  colorInterface: (name: string) => `[${name}{bg:palegreen}]`, 
  class: (name: string, props: PropertyDetails[], methods: MethodDetails[]) => { 
    const pTemplate = (property: PropertyDetails) => `${property.name};`; 
    const mTemplate = (method: MethodDetails) => `${method.name}();`; 
    return ( 
      `${templates.colorClass(name)}` + 
      `[${name}|${props.map(pTemplate)}|${methods.map(mTemplate)}]` 
    ); 
  }, 
  interface: ( 
    name: string, 
    props: PropertyDetails[], 
    methods: MethodDetails[] 
  ) => { 
    const pTemplate = (property: PropertyDetails) => `${property.name};`; 
    const mTemplate = (method: MethodDetails) => `${method.name}();`; 
    return ( 
      `${templates.colorInterface(name)}` + 
      `[${name}|${props.map(pTemplate)}|${methods.map(mTemplate)}]` 
    ); 
  } 
}; 

以下函数用于获取给定源文件的 AST:

function getAst(tsConfigPath: string, sourceFilesPaths?: string[]) { 
  const ast = new Ast({ 
    tsConfigFilePath: tsConfigPath, 
    addFilesFromTsConfig: !Array.isArray(sourceFilesPaths) 
  }); 
  if (sourceFilesPaths) { 
    ast.addExistingSourceFiles(sourceFilesPaths); 
  } 
  return ast; 
} 

以下函数生成类声明的 yUML DSL。我们遍历 AST,寻找属性和方法:

function emitClass(classDeclaration: SimpleAST.ClassDeclaration) { 

  const className = classDeclaration.getSymbol()!.getName(); 
  const propertyDeclarations = classDeclaration.getProperties(); 
  const methodDeclarations = classDeclaration.getMethods(); 

  const properties = propertyDeclarations.map(property => { 
      const sym = property.getSymbol(); 
      if (sym) { 
          return { 
              name: sym.getName() 
          }; 
      } 
  }).filter((p) => p !== undefined) as PropertyDetails[]; 

  const methods = methodDeclarations.map(method => { 
    const sym = method.getSymbol(); 
    if (sym) { 
        return { 
            name: sym.getName() 
        } 
    } 
  }).filter((p) => p !== undefined) as MethodDetails[]; 

  return templates.class(className, properties, methods); 
} 

以下函数生成接口声明的 yUML DSL。我们遍历 AST,寻找属性和方法:

function emitInterface(interfaceDeclaration: SimpleAST.InterfaceDeclaration) { 

  const interfaceName = interfaceDeclaration.getSymbol()!.getName(); 
  const propertyDeclarations = interfaceDeclaration.getProperties(); 
  const methodDeclarations = interfaceDeclaration.getMethods(); 

  const properties = propertyDeclarations.map(property => { 
      const sym = property.getSymbol(); 
      if (sym) { 
          return { 
              name: sym.getName() 
          } 
      } 
  }).filter((p) => p !== undefined) as PropertyDetails[]; 

  const methods = methodDeclarations.map(method => { 
      const sym = method.getSymbol(); 
      if (sym) { 
          return { 
              name: sym.getName() 
          } 
      } 
  }).filter((p) => p !== undefined) as MethodDetails[]; 

  return templates.interface(interfaceName, properties, methods); 
} 

以下函数生成继承子句的 yUML DSL。这包括通过使用 extends 和 implements 关键字在源代码中定义的关系:

function emitInheritanceRelationships( 
  classDeclaration: SimpleAST.ClassDeclaration 
) { 

  const className = classDeclaration.getSymbol()!.getName(); 
  const extended =  classDeclaration.getExtends(); 
  const implemented =  classDeclaration.getImplements(); 
  let heritageClauses: HeritageClause[] = []; 

  if (extended) { 
      const identifier = extended.getChildrenOfKind(ts.SyntaxKind.Identifier)[0]; 
      if (identifier) { 
          const sym = identifier.getSymbol(); 
          if (sym) { 
              heritageClauses.push( 
                  { 
                      clause: sym.getName(), 
                      className 
                  } 
              ); 
          } 
      } 
  } 

  if (implemented) { 
      implemented.forEach(i => { 
          const identifier = i.getChildrenOfKind(ts.SyntaxKind.Identifier)[0]; 
          if (identifier) { 
              const sym = identifier.getSymbol(); 
              if (sym) { 
                  heritageClauses.push( 
                      { 
                          clause: sym.getName(), 
                          className 
                      } 
                  ); 
              } 
          } 
      }); 
  } 

  return flatten(heritageClauses).map((c: HeritageClause) => 
    templates.implementsOrExtends(c.clause, c.className) 
  ); 

} 

以下函数使用 yUML 网络服务将 UML 图表渲染到 .png 文件。我们使用 request npm 模块调用该网络服务:

request 模块用于下载渲染后的图像。然后使用 Node.js 文件系统 API 将图像保存到当前目录:

function render(dsl: string) { 
  const download = (uri: string, filename: string, callback: () => void) => { 
    request.head(uri, (err, res, body) => { 
      request(uri) 
        .pipe(fs.createWriteStream(filename)) 
        .on("close", callback); 
    }); 
  }; 

  const url = templates.url(dsl); 
  const file = `uml_diagram_${new Date().getTime()}.png`; 
  const absolutePath = path.join(__dirname, file); 

  download(url, file, () => 
    console.log(`Saved UML diagram available at ${absolutePath}`) 
  ); 
} 

以下函数为给定的 TypeScript 文件生成 yUML DSL。此函数将工作委托给先前定义的 getAst、emitClass、emitInterface 和 emitInheritanceRelationships 函数:

function yUML(tsConfigPath: string, sourceFilesPaths: string[]) { 
  const ast = getAst(tsConfigPath, sourceFilesPaths); 
  const files = ast.getSourceFiles(); 

  const declarations = files.map(f => { 
    return { 
      fileName: f.getFilePath(), 
      classes: f.getClasses(), 
      interfaces: f.getInterfaces() 
    }; 
  }); 

  const entities = declarations.map(d => { 
    const classes = d.classes.map(emitClass); 
    const interfaces = d.interfaces.map(emitInterface); 
    const inheritanceRelationships = d.classes.map( 
      emitInheritanceRelationships 
    ); 
    return [...classes, ...interfaces, ...inheritanceRelationships]; 
  }); 

  return join(flatten(entities), ","); 
} 

到目前为止,我们已经实现了整个应用程序,并且可以调用 yUML 函数来为给定的文件生成 yUML DSL:

const yuml = yUML("./tsconfig.json", [ 
  "./app/interfaces.ts", 
  "./app/ninja.ts", 
  "./app/katana.ts", 
  "./app/main.ts" 
]); 

最后,我们可以调用 render 函数:

render(yuml); 

请参阅官方 ts-simple-ast 文档 dsherret.github.io/ts-simple-ast/ 了解更多关于可用 API 的信息。

VS Code 扩展

开发 Visual Studio 扩展超出了本书的范围。然而,值得一提的是,可以使用 TypeScript 开发 VS Code 扩展。这意味着我们可以将我们的自定义命令行 TypeScript 工具,如 UML 图生成器,转换成 VS Code 扩展,而不会遇到太多复杂的问题。

请参考关于在 code.visualstudio.com/docs/extensions/overview 开发扩展的官方 VS Code 文档以获取更多信息。

摘要

在本章中,我们学习了 TypeScript 编译器的内部组件。我们还学习了如何使用编译器 API 以及如何利用这些特性来开发我们的 TypeScript 开发工具。

我希望这一章能够激发你对 TypeScript 编译器内部结构和由 TypeScript 驱动的软件开发工具开发的兴趣。它们的潜力和日益增长的受欢迎程度将把 JavaScript 生态系统提升到新的水平。

Vue 2.x TypeScript 按需引入element-ui 在业务中,只要使用组件库,就不可避免的要涉及到按需引入的问题,尤其是element这种重量级的组件库,如果一次性全部引入,对整个项目的大小影响很大。 但是,element官方只提供了基于babel的按需引入方案,并没有给出ts应该如何做到按需引入。经过试验,如果不做处理,哪怕是import { Button, Select } from 'element-ui';,在ts中还是会全部引入。 本来是... 阅读详情

相关推荐

vue 2.x 接入 typescript

对于新项目,通过vue-cli手动选择需要的特性就可以很方便的接入 typescript,但对于一开始没有引入 typescript 的 vue 项目,再进行接入就较为繁琐了,在这里整理一下完整的引入方式。 安装依赖 项目根目录下执行: 1 npm install --save-dev typescript ts-loader@8.2.0 fork-ts-checker-webpack-plugin ...

cherrow的博客 843

HarmonyOS ArkTS 实战:基于 QRCode 组件打造自定义分享二维码弹窗

通过装饰器封装组件,整合测试信息、QRCode、用户信息等 UI 元素,实现弹窗的视觉与功能布局:typescript运行// 自定义分享弹窗组件// 接收外部传递的测试详情(单向数据绑定)// 从Auth工具类获取当前用户信息// 弹窗控制器(必须定义,用于关闭弹窗等操作)build() {// Stack布局:实现“保存到本地”按钮悬浮在弹窗右下角// 弹窗主体内容(垂直布局)// 1. 品牌Logo.width(40)// 2. 品牌/功能文案。

qq_36235619的博客 4620

10 个 TypeScript 常见错误及解决方案(前端必看)

面向初中级 TypeScript 开发者,聚焦实际开发场景中的典型错误模式。涵盖类型系统、模块机制、编译配置等核心领域。每个错误单元包含:错误场景 → 问题本质 → 解决方案 → 防御策略 → 代码演示// 类型断言:相当于告诉编译器"我知道这里的具体类型"// 类型推断:编译器自动推导变量类型// 自动推断为 number 类型// 泛型:可复用的类型模板T错误现象解决方案// 方案1:扩展接口age?: number;// 可选属性// 方案2:类型断言(已知特定场景)

小白菜的博客 2092

使用 TypeScript 创建 Elasticsearch MCP 服务器

MCP 是Anthropic 公司开源的开放通信标准,核心作用是建立LLM 应用 ↔ 外部系统(Elasticsearch、数据库、文件系统等)的安全双向连接,替代传统自定义接口、专属插件的碎片化对接方式。核心特性:标准化工具注册、结构化数据传输、多客户端兼容、轻量通信;通信原理:MCP 服务器对外暴露「工具」,MCP 客户端主动发现并调用这些工具,实现数据互通;传输方式:本文采用Stdio(标准输入输出流)传输,客户端以子进程形式启动服务器,无需额外端口,是本地 MCP 服务的最优轻量方案。维度。

海兰 1804

YOLO系列最新改进:TSCODE——一种创新的特定任务检测头

本文介绍了YOLO系列最新改进中的TSCODE特定任务检测头。通过解耦特定任务的上下文信息,TSCODE能够为目标检测任务提供高效的支持。我们提供了TSCODE的设计原理和使用方法,并给出了相应的源代码供读者参考和实践。通过引入TSCODE特定任务检测头,我们相信YOLOv7目标检测器的性能将得到进一步提升,为计算机视觉领域的目标检测任务带来更多创新和突破。import cv2# 加载YOLOv7模型和权重# 加载TSCODE模型和权重# 加载图像# 图像预处理# 目标检测# 特定任务检测。

IbcVue的博客 3138

IEEE Transactions模板中参考文献作者缩写、期刊名缩写

本文章记录如何在IEEE Transactions的模板中,解决参考文献的作者缩写、期刊名字缩写的问题。

weixin_43981621的博客 1万+

TypeScript 2.x 学习指南(一)

在过去的十年中,平均 Web 应用程序的 JavaScript 代码库呈指数级增长。然而,当前版本的 JavaScript 是几年前设计的,缺乏一些应对现代 JavaScript 应用程序可能遇到的复杂程度所必需的特性。由于这些缺失的特性,维护性问题已经出现。ECMAScript 2015规范旨在解决 JavaScript 的一些维护性问题,但其实现仍在进行中,许多不兼容的 Web 浏览器仍在使用。因此,广泛采用ECMAScript 2015规范预计将是一个缓慢的过程。为了解决 JavaScript 的维护

龙哥盟 1245

基于Web的智能家居(源码+开题)

实践上,智能家居系统的普及和应用将极大改善人们的居住体验,提高生活质量,同时也有助于促进智能家居产业链的发展和完善,为社会经济发展注入新的活力。此外,本研究还将对基于Web的智能家居系统的用户体验进行深入研究,通过用户调研和数据分析,不断优化系统功能和界面设计,提升用户的使用体验和满意度。针对以上问题,本研究将综合运用物联网、云计算、大数据等新一代信息技术,通过系统架构设计、功能模块开发、安全性保障、用户体验优化等多个方面的研究工作,力求为基于Web的智能家居系统的研发和应用提供全面而有效的解决方案。

sryue06的博客 1904

TypeScript 学习指南

/ 定义API响应接口T;// 用户接口id: number;// API调用示例${// 定义API响应接口 interface ApiResponse < T > {T;} // 用户接口 interface User {} // API调用示例 async function fetchUser(id : number) : Promise < ApiResponse < User >> {`);

zsr154278963的博客 1635

GD32F303片内FLASH实战:从地址映射到数据持久化存储

本文详细介绍了GD32F303片内FLASH的实战应用,从地址映射到数据持久化存储的全过程。通过解析FLASH的基础特性、安全操作区域规划、擦除写入读取的三重奏操作,以及工程化进阶技巧,帮助开发者高效利用片内FLASH进行数据存储。文章还提供了调试技巧和常见问题解决方案,特别适合嵌入式开发者在实际项目中应用。

weixin_26742753的博客 216

从防御者视角复盘:用WinArpAttacker和Wireshark在Windows上模拟并检测ARP欺骗攻击

本文详细解析了Windows平台下的ARP欺骗攻击与防御策略,通过WinArpAttacker和Wireshark工具模拟攻击并检测异常流量。文章涵盖实验环境搭建、攻击原理深度解析、流量特征分析及企业级防御方案,帮助安全运维人员全面掌握ARP攻击检测与防护技术。

weixin_30918633的博客 121

搭建你的项目脚手架 — vue3.x + typescript4.x + vite2.x + pinia2.x

搭建你的项目脚手架 — vue3.x + typescript4.x + vite2.x + pinia2.x

weixin_43822185的博客 1516

医学影像AI的幻觉问题与CCD解决方案

医学影像AI在辅助诊断中面临的核心挑战是幻觉现象,即模型生成不符合实际影像的虚假诊断。这种现象源于多模态大语言模型(MLLMs)在视觉-语言对齐上的技术局限,可能导致假阳性或假阴性的临床风险。通过临床对比解码(CCD)框架,结合症状锚定和专家引导的双阶段校准机制,可有效提升模型输出的可靠性。该技术在CheXpert和MIMIC-CXR等标准数据集上验证,显著改善了肺不张、胸腔积液等关键指标的检测准确率。对于医疗AI落地场景,这种即插即用的解决方案既规避了数据隐私合规问题,又降低了基层医院的部署门槛,为AI辅

weixin_36303807的博客 322

TypeScript 2.x 学习指南(四)

");cb();}, 1000);});在本章中,我们学习了如何使用一系列不同的开发工具。在一章中深入探讨这么多工具是不可能的,但现在我们已经了解了基础知识,逐步深入到更高级的使用案例应该会容易得多。在下一章中,我们将学习使用 Node.js 和 TypeScript 开发后端应用程序。在本章中,我们学习了 Node.js 作为平台的主要特性和其主要组件。我们还了解了一些关于 Node.js 生态系统以及我们可以使用它创建的应用类型。在本章的结尾,我们实现了一个非常小的 REST API。

龙哥盟 1359

2021-08-20

通过QQ号获取绑定的手机号 导语 偶有奇想,我们可以通过手机号来搜索获取其对应的QQ号,那么,反过来呢? 一.缩小范围 由于手机号属于隐私数据,腾讯QQ并没有直接给出通过QQ号查找对应手机号的接口。 国内的手机号有十一位数字,从数学的角度上来说,包含了1011种可能。如果遍历这1011个数据,通过QQ中手机号查找QQ号的接口来寻找,未免太过费时费力,根据网上查到的资料,同一个QQ号,在十分钟内只能进行30次查询(未验证)。因此,直接遍历的方法不现实。 在这10^13个数据中,包含着大量的可去除的无用数据。我

m0_56063822的博客 4722

Android功耗系列专题理论之一:功耗优化分析思路简介

本文系统介绍了电子系统功耗分析方法,涵盖静态与动态功耗原理、电流波形分析技术和模块化拆解策略。重点阐述了PMIC与CPU协同管理机制、DVFS动态调节技术,以及通过示波器捕获波形特征定位异常的方法。针对亮屏等典型场景,提出三阶段对比分析法,并详细说明硬件/软件拆解方案及混合验证策略。文章还总结了常见问题模块(CPU、多媒体、外设)的排查流程,强调需结合日志分析、温度控制及重复测试确保数据可靠性,为功耗优化提供系统化解决方案。

1459

TypeScript 2.x 学习指南(三)

在阅读这本书之后,你可能会渴望开始一个新的项目,将你迄今为止所学的一切付诸实践。在这个时候,你应该能够编写一个使用 TypeScript 的小型网络应用程序,并解决你可能会遇到的潜在设计时问题。然而,随着你的新项目不断发展,你开发了更多复杂的功能,你可能会遇到一些运行时问题。本章应该为你提供解决运行时问题的缺失知识。我们在前面的章节中只是简要地提到了 TypeScript 运行时,但根据你的背景,你可能已经对它有了很多了解,因为 TypeScript 运行时是 JavaScript 运行时。TypeScri

龙哥盟 1139

编织数据的实时脉络——构建基于MySQL的数据编织平台,实现实时数据治理

除了上述基本设置外,我们还可以根据实际应用场景自定义一些特定的监控项目。例如,对于某些高并发读写的表,可能需要特别关注它们的锁等待时间和回滚段使用率;而对于涉及到跨库关联查询的操作,则应当着重检查网络延迟及缓存命中率等因素。-- 获取InnoDB行级锁等待情况-- 统计各表的缓存命中率这段SQL脚本不仅展示了如何查询InnoDB引擎下的锁争用状况,还介绍了计算表级别缓存命中率的方法,有助于深入理解系统的运行状态。

墨夶的博客 1789

基于Vue2.x + Vite2.x + TypeScript 项目开发框架模板

基于Vue2.x + Vite2.x + TypeScript 项目开发框架模板 仓库地址:https://gitee.com/chenzhihuiShuai/vite-vue2.git 主要依赖 名称 版本 说明 vue 2.6.X vue核心依赖,项目基于vue2.6版本构建,而非vue3.X vue-router 3.5.X vue2.X版本路由 vuex 3.6.X vue2.X版本的vuex @vue/composition-api 1.X.X 适用于vue2.x版本

qq_37682086的博客 1033

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

TypeScript 2.x 学习指南(五)

在本章中,我们将学习如何使用 Angular 和 TypeScript 开发前端网页应用程序。就像在上一章一样,我们将尝试使用我们之前实现的后端 Node.js 应用程序。我们将开发的应用程序是上一章中开发的前端应用程序的克隆。应用程序的功能和样式将保持一致。然而,实现方式将呈现一些显著差异,因为我们打算使用 Angular 而不是 React 作为我们的前端应用程序开发框架。我们不会在本章中详细介绍我们将要实现的应用程序的需求,因为我们已经在上一章中讨论过了。Angular 是一个库,允许我们实现网页用户

龙哥盟 1124

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

TypeScript 2.x 学习指南(二)

在上一章中,我们学习了如何使用函数以及如何利用一些异步编程 API。在本章中,我们将学习如何使用面向对象编程(OOP)范式实现 TypeScript 应用程序。我们将学习以下主题:类关联、聚合和组合继承混合泛型类泛型约束接口SOLID 原则我们应该已经熟悉了 TypeScript 类的基础知识,因为我们已经在之前的章节中声明了一些。现在,我们将通过示例来查看一些细节和 OOP 概念。让我们从一个简单的类开始声明: 我们使用类来表示对象或实体的类型。一个类由一个名称、属性(也称为属性)和方法组成。方法和属性都

龙哥盟 771

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

TypeScript 全面学习指南 (2025最新版)

TypeScript 全面学习指南

zhwanwangwang的博客 1722

The Concise TypeScript Book:免费 TypeScript 7 学习指南

The Concise TypeScript Book 是一本免费开源的 TypeScript 7 学习指南,全书 62 章、按 TypeScript 7.0 编写。这篇文章带你按顺序解决三件事:编译器怎么装、tsconfig.json 怎么配、从 6.0 迁到 7.0 哪些红线不能碰,最后给出一条按角色划分的章节选读路线。 ## 60 秒速览:这本书是什么、给谁用 | 维度 | 内容 | |

gitblog_00508的博客 391

探索 TypeScript 技术世界:全面学习指南

学习TypeScript是一项既有深度又有广度的任务,涉及类型系统、面向对象编程、函数式编程、模块化、工程化等多个方面。遵循上述学习路径,理论与实践相结合,逐步积累经验,您将能够在实际项目中游刃有余地运用TypeScript,提升开发效率与代码质量。同时,持续关注TypeScript的最新发展动态和技术趋势,保持知识。

聚焦于深度解析最新的编程语言特性、框架升级、架构设计、开发工具和最佳实践,涵盖Web前端(如Vue3, React, Angular等)、后端开发(如Node.js, Java, Python等)、移动端开发(iOS, Android原生及跨平台开发) 3万+

TypeScript学习指南

一、TypeScript是什么? 它是以js未基础构建的语言 它是js的一个超集,换句话说就是对js的一个扩展(增强) 它可以在任何支持js的平台中执行,但TS不能被js解析器直接执行 TS扩展了js,并添加了类型 浏览器中不能直接执行TS,需要经过编译后转成js后才行 二、TypeScript增加了什么? 增加了类型(这里说的类型是除js的数据类型外,增加了变量的类型等,就是在类型这块做了强化) 支持ES新特性 增加了一些ES中不具备的新特性(抽象类、工具、接口、装饰器等) 丰富的配置选项 强大的开

Ronychen的博客 3588

typeScript学习指南

typescript学习/指南

qq_44599159的博客 2010
上一篇: TypeScript 2.x 学习指南(五)
下一篇: UE 安卓游戏开发学习手册(一)
绝不原创的飞龙
绝不原创的飞龙 优质创作者: 人工智能、数据科学与机器学习技术领域 优质创作者: 人工智能、数据科学与机器学习技术领域
博客等级 码龄15年 4万+粉丝 2万+原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值