探索TypeScript中的高级编译器选项

401 阅读7分钟

简介

tsconfig.json 文件指定了TypeScript编译器使用的编译选项,它对我们的代码进行检查,并决定是否有任何检查失败。这些选项包括我们的TypeScript代码将被编译为哪个版本的JavaScript,输出目录应该是什么,以及是否允许项目目录中的JavaScript源文件。

在这篇文章中,我们将深入了解一些高级编译器选项和其他一些选项,它们可以帮助我们发现TypeScript代码库中的潜在问题。深入了解这些编译器选项以及导致代码无法通过严格检查规则的原因,将有助于我们在构建TypeScript应用程序时避免常见错误。

具体来说,这篇文章将涵盖以下选项。

  • 嵌套的tsconfig.json 文件
  • [strictPropertyInitialization](#strictpropertyinitialization)
  • [noImplicitThis](#noimplicitthis)
  • [noImplicitReturns](#noimplicitreturns)
  • [strictNullChecks](#strictnullchecks)

嵌套的tsconfig.json 文件

TypeScript编译器在编译当前目录下的代码时可以引用另一个目录下的tsconfig.json 文件。

如果我们想在特定目录下运行tsc 时引用一个编译器选项,这个功能就很方便。tsconfig.json 文件为此目的使用了"references" 选项。

作为这种嵌套配置的一个例子,请考虑下面的源代码树。

├── dist
└── src
    ├── tsconfig.json
    ├── backend
    │   ├── index.ts
    │   └── tsconfig.json
    └── frontend
        ├── index.ts
        └── tsconfig.json 

在这里,我们在项目的src 目录下有一个tsconfig.json 文件,以及两个名为frontendbackend 的子目录。这两个子目录包含一个tsconfig.json 文件和一个名为index.ts 的TypeScript文件。

项目的src 目录中的tsconfig.json 文件如下。

{
    "compilerOptions": {
      "target": "es5", 
      "module": "commonjs", 
      "rootDir": ".",
      "outDir": "../dist/",
    },
    "files": [],
    "references": [
      { "path": "./backend" },
      { "path": "./frontend" }
    ]
  }

在这里,我们指定了outDir 属性来生成所有的JavaScript输出到dist 目录中,接着配置了两个子目录的参考路径。

整个项目可以用以下命令编译。

tsc --build src

让我们来看看backend 目录中的tsconfig.json 文件,如下所示。

{
    "compilerOptions": {
      "rootDir": ".",
      "outDir": "../../dist/backend",
    }
  }

在这里,我们指定了outDir 属性来生成所有的JavaScript输出到dist 目录中。

这意味着TypeScript编译器将把这个目录中的所有JavaScript文件输出到dist 目录,这个目录是向上两层的。

frontend 子目录可以使用以下命令独立构建。

tsc --build src/frontend

让我们来看看backend 目录中的tsconfig.json 文件。

{
    "compilerOptions": {
      "rootDir": ".",
      "outDir": "../../dist/frontend",
    },
    "references": [
      { "path": "../backend" }
      "composite": true
    ]
  }

同样,我们指定了outDir 属性,将这个目录下的所有JavaScript输出生成到dist 目录,这个目录是向上两级的,接着配置了backend 子目录的参考路径。

请注意TypeScript文档中的这些信息:"被引用的项目必须启用新的composite 设置。这个设置是必要的,以确保TypeScript能够快速确定在哪里找到被引用项目的输出。"

此外,backend 子目录可以使用以下命令独立构建。

tsc --build src/backend

strictPropertyInitialization

当启用时,strictPropertyInitialization 编译器选项确保类内的所有属性被正确初始化。

让我们考虑一下下面的类定义。

class NoInitProperties { 
  a: number; 
  b: string; 
}

在这里,我们有一个名为NoInitProperties 的类,它有两个属性,分别是number 类型的astring 类型的b 。上述代码将产生以下错误。

error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor 
error TS2564: Property 'b' has no initializer and is not definitely assigned in the constructor

产生这些错误是因为该类的ab 两个属性都没有被初始化。

解决strictPropertyInitialization 的问题

我们有四种方法可以修复这段代码。

第一种方法通常用于修复这些错误,它使用一个构造函数。

class NoInitProperties { 
    a: number; 
    b: string; 
    constructor(b: string) { 
      this.a = 5; 
      this.b = b; 
    } 
}

在这里,我们定义了一个constructor 函数,参数为b ,类型为string 。在constructor 中,我们已经将b 参数的值分配给内部的b 属性。此外,我们还将字符串值"letter" 赋给了名为a 的属性。有了这个构造函数,
,错误将被修复,因为现在两个属性都被正确地初始化了。

第二种方法是使用一个类型联盟。

class NoInitProperties { 
    a: number | undefined; 
    b: string | undefined; 
}

这里,联合类型被用来将undefined 类型添加到ab 两个属性中。有了这个,编译器知道我们意识到这些属性可能是未定义的,这将使我们能够自己处理后果。

我们可以用来修复这些错误的第三个方法是使用明确的赋值断言运算符。

class NoInitProperties { 
    a!: number; 
    b!: string; 
}

在每个属性后面添加的! 操作符告诉编译器,我们意识到这些属性还没有被初始化。

修复这些错误的第四种方法是为这些属性中的每一个赋值。

class NoInitProperties { 
    a: number = 5; 
    b: string = "letter"; 
}

在这里,我们将5 的数字值分配给了a属性,将"letter" 的字符串值分配给了b 属性。

noImplicitThis

noImplicitThis 编译器选项将确保正确访问this 关键字,否则编译器将抛出一个错误,表明对this 的访问不正确。
让我们考虑以下代码。

class NoImplicitThisClass { 
    name: string = "Tom"; 
    logToConsole() { 
        let callback = function () { 
            console.log(`this.name : ${this.name}`); 
        } 
      setTimeout(callback, 1000); 
    } 
}

在这里,我们有一个名为noImplicitThisClass 的类,它有一个name 的属性,初始化为一个字符串值Tom

同时,该类定义了一个名为logToConsole 的函数,当被调用时,两秒后触发函数callback 。这个类的使用方法如下。

let instanceOfClass = new NoImplicitThisClass(); 
instanceOfClass.logToConsole();

在这里,我们创建了一个名为instanceOfClass 的变量来保存一个NoImplicitThisClass 的实例,调用logToConsole 函数将输出以下内容。

this.name : undefined

下面是发生的情况:this 属性没有引用NoImplicitThisClass 类。这是由于this 属性在JavaScript中的作用域。在JavaScript中,方法中的this 范围默认不与任何引用绑定。

如果打开了noImplicitThis 编译器选项,编译器将产生以下错误。

error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation

在这里,编译器通知我们,我们在回调函数中对this.name 的引用没有引用到NoImplicitThisClass 类的this 属性。

解决noImplicitThis 的问题

这个错误可以通过将this 属性传递到callback 函数中来解决,如下所示。

let callback = function (_this) { 
    console.log(`this.name : ${_this.name}`); 
} 
setTimeout(callback, 2000, this);

在这里,我们在回调函数中添加了一个名为_this 的参数,然后将这个参数的值传递到setTimeout 的调用中。

解决这个错误的另一个常见方法是使用箭头函数。这在React中非常常见。

let callback = () => { 
    console.log(`this.name : ${this.name}`); 
} 
setTimeout(callback, 2000)

在这里,我们用箭头函数的语法替换了函数关键字。

这两种解决方案都会有如下结果。

this.name : Tom

在我的React项目中,我经常使用箭头函数来处理this ,我建议使用箭头函数,因为它要干净得多。

noImplicitReturns

noImplicitReturns 编译器选项将确保每个声明了返回值的函数必须返回函数中定义的值。
让我们考虑一下下面的代码。

function fetchUsernameById(id: number): string { 
    if (id === 2) return "Sam"; 
} 
console.log(`fetchUsernameById(4) : ${fetchUsernameById(4)}`)

这里,fetchUsernameById 有一个数字类型的参数id ,并返回一个字符串值。该函数检查作为参数传入的值是否等于2 。如果是,它返回字符串值Sam 。然而,如果参数的值不等于2 ,则不返回任何东西。

这将是运行这段代码的输出结果。

fetchUsernameById(4) : undefined

在这里,我们可以看到,对于任何不等于2 的参数值,fetchUsernameById 函数都会返回undefined
如果noImplicitReturns 编译器选项设置为true ,编译器将产生一个错误。

error TS7030: Not all code paths return a value.

解决noImplicitReturns 的问题

这个错误可以通过对不等于2 的id返回一个字符串值来解决。

function fetchUsernameById(id: number): string { 
    if (id === 2) 
      return "Sam"; 
    return "No user with such id"
}

在这里,我们在函数的末尾添加了一个返回语句,只要函数被调用,参数的值不等于2 ,就会返回字符串 "No user with such id"。

strictNullChecks

strictNullChecks 编译器选项用于在我们的代码中找到变量的值在使用时可能是nullundefined 的情况。
让我们考虑一下下面的代码。

let a: number; 
let b = a;

上述代码将产生以下错误。

error TS2454: Variable 'a' is used before being assigned

这个错误告诉我们,当变量a 还没有被赋值时,就使用了它的值。
技术上来说,a 的值可能是undefined

解决strictNullChecks 的问题

这个错误可以通过确保变量a 在使用前被赋值来解决。

let a: number = 4; 
let b = a;

在这里,我们只是将4 的值赋给了名为a 的变量,这就可以解决这个错误。

另一个解决这个错误的方法是使用联合类型来通知编译器,我们知道在使用时变量可能没有被赋值,我们将自己处理这个后果。

let a: number | undefined; 
let b = a;

结论

在这篇文章中,我们看了用于配置TypeScript编译器的各种高级编译器选项。我们还看到了与每个编译器选项相关的错误信息以及如何解决这些问题。

请查看Typescript官方文档,了解更多的编译器选项

The postExploring advanced compiler options in TypeScriptappeared first onLogRocket Blog.