大家好,欢迎来到IT知识分享网。
- include, exclude, files配置项
- extends配置
- compilerOptions下的配置
- compilerOptions.allowUnreachableCode
- compilerOptions.allowUnusedLabels
- compilerOptions.alwaysStrict
- compilerOptions.exactOptionalProperties
- compilerOptions.downlevelIteration
- compilerOptions.importHelpers
- compilerOptions.strict
- compilerOptions.strictBindCallApply
- compilerOptions.strictFunctionTypes
- compilerOptions.strictNullChecks
- compilerOptions.strictPropertyInitialization
- compilerOptions.noImplicitAny
- compilerOptions.noImplicitOverride
- compilerOptions.noImplicitReturns
- compilerOptions.noImplicitThis
- compilerOptions.noPropertyAccessFromIndexSignature
- compilerOptions.noUncheckedIndexedAccess
- compilerOptions.noUnusedLocals
- compilerOptions.noUnusedParameters
- compilerOptions.useUnknownInCatchVariables
- 小结
- 我对typescript一些看法
基于typescript的项目的根目录下都会有一个文件(tsconfig.json
), 这个文件主要用来控制typescript编译器(tsc, typescript compiler)的一些行为, 比如指定哪些文件需要让tsc来编译, 哪些文件不想让tsc进行编译之类的.
angular项目的tsconfig.json
文件
tsconfig.json
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictTemplates": true
}
}
这其中angularCompilerOptions
顾名思义是angular专用的, 不在本文讨论范围.
include, exclude, files配置项
include
: 指定要编译哪些文件, 比如只需要编译<project-dir>/src
目录下的.ts源代码文件
{
"compilerOptions": {
...
},
include: ["./src/**/*", "./demo/**/*.tsx?"]
}
上面的include配置用到了两个通配符: **/
, *
**/
表示匹配任何子路径, 包括目录分隔符/
也会被它匹配, 所以用来这个通配符后, 目录下有多少子目录都会被匹配到
*
表示匹配除了目录分隔符(/
)外的任何长度的字符串
?
表示匹配一个除文件分隔符(/
)外的任一字符
显然./src/**/*
即表示匹配src
文件夹下的任何子文件夹的任何文件; 而./demo/**/*.tsx?
即表示匹配demo
目录下任何子目录下的任意以.ts
或.tsx
结尾的文件
include其实就是一个白名单, 在这个白名单里被匹配到的文件才会被tsc处理编译
相对于include
是作为白名单的配置, exclude
选项就是一个黑名单了, 它的值和include一样是一个路径名字符串数组, 最常见的用处就是用来排除掉node_modules
目录下的文件
{
"compilerOptions": {
...
},
include: ["./src/**/*", "./demo/**/*.tsx?"],
exclude: ["node_modules/**/*"]
}
当然也可以用exclude
排除掉include
里面包含到的文件
有些情况即使exclude了某些文件后, 编译后的代码中可能仍然包含被
exclude
了的内容, 比如通过import
导入了被exclude
了的node_modules
文件夹, 此时tsc仍然会去处理被exclude了的文件, 这一点应该不难理解
files
配置的作用类似include
, 也是一个白名单路径数组, 不同在于它不能使用通配符, 而必须使用精确的文件路径(可以是相对路径), 比如如果项目只有一个入口文件, 那么就可以使用在只用files
配置这个文件的路径, 然后其他的文件都通过index.ts
来import
tsconfig.json
{
"compilerOptions": {
...
},
// include: ["./src/**/*", "./demo/**/*.tsx?"],
// exclude: ["node_modules/**/*"]
files: ['./src/index.ts']
}
extends配置
extends
用于在一个tsconfig.json
文件中扩展其他tsconfig.json
文件, 比如angular项目中有三个tsconfig配置文件: tsconfig.json
, tsconfig.spec.json
, tsconfig.app.json
tsconfig.json
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
},
...
}
tsconfig.app.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
tsconfig.spec.json
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
...
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
从命名和文件内容上即可看出之所以这么做是为了针对测试文件.spec.ts
和普通.ts
文件在使用不同的配置时又能共享他们相同部分的配置, 达到避免重复的目的
compilerOptions下的配置
compilerOptions.allowUnreachableCode
表示是否允许代码中出现永远无法被执行到的代码, 可选值是undefined
, false
, true
{
"compilerOptions": {
"allowUnreachableCode": false
...
},
...
}
什么是”永远无法被执行到的代码”?
const foo = () => {
return 0;
console.log('aaa'); // 这一行代码就是永远被执行到的代码
}
配置为undefined
时, 遇到这种情况会给出warning, 配置false则直接编译时抛出错误无法成功编译, 配置为true既没有警告也没有错误
compilerOptions.allowUnusedLabels
这个选项是针对标签(label)语法的, 这个语法很罕见, 我也是看到了这个配置才知道有这个原来js还有Label
语法, label语法有点像其他语言里的goto, 真是场景中几乎不用
compilerOptions.allowUnusedLabels
表示是否允许未使用到的标签
可选项:
undefined
: 这是默认值, 碰到未使用的标签给出warning警告false
: 碰到未使用的标签抛出错误, 编译失败true
: 碰到未使用的标签编译通过, 且不给出异常
function bar() {
console.log('loafing...');
loop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 2) {
// break loop;
}
console.log(i, j, i + j);
}
}
}
compilerOptions.alwaysStrict
默认值是true
, 开启这个选项保证输出的js代码处于ECMAScript标准的严格模式下, 也就是js文件里的use strict
compilerOptions.exactOptionalProperties
这是typescript4.4中才加入的一个选项, 默认处于不开启状态; 开启此选项, typescript会对可空属性执行更严格的类型检查, 可空属性只有在初始化时可以留空为undefined
, 但是不能被手动设置为undefined
例如有一个IFoo
接口
interface IFoo {
foo?: string;
}
在compilerOptions.exactOptionalProperties = false
情况下
const obj: IFoo = {};
obj.foo = '1111';
console.log(obj.foo);
obj.foo = undefined;
console.log(obj.foo);
这段代码可以正常编译通过
但如果开启compilerOptions.exactOptionalProperties
选项后情况就不同了
const obj: IFoo = {};
obj.foo = '1111';
console.log(obj.foo);
// 编译器会报: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target.
obj.foo = undefined;
console.log(obj.foo);
// 这一行会报: Type '{ foo: undefined; }' is not assignable to type 'IFoo' with 'exactOptionalPropertyTypes: true'.
const obj2: IFoo = {
foo: undefined
}
compilerOptions.downlevelIteration
先解释下什么是Downleveling
? Downleveling
是Typescript中的一个术语, 它表示将typescript代码转译为相对更低版本的javascript
这个标志位模式是不开启的.
开启这个标志位typescript会生成一个帮助方法来对es6中的for of
和数组展开([...arr]
)语法进行转译, 以兼容es3/es5
下面的示例用for of
循环并输出一个包含符号表情的字符串:
const text = `(😜)`;
for (const c of text) {
console.log(c);
}
如果配置typescript的compilerOptions.target
选项为es6
及以上, 那么不管有没有开启compilerOptions.downlevelIteration
, 输出都是:
(
😜
)
现在把compilerOptions.target
设置为es5
并关闭compilerOptions.downlevelIteration
, 输出:
控制台中一共有四行, 中间两行是两个乱码, 这跟我们的预期的就不一样了, 我们预期应该只有三行输出, 实际结果确是符号表情被分成了两个乱码字符输出了;
这是因为没开启compilerOptions.downlevelIteration
, typescript处理for of
时会直接转译成经典的索引迭代(for (var _i = 0, text_1 = text; _i < text_1.length; _i++)
), 而符号表情实际上占了2个字节的存储, 所以自然就会将符号表情分成两个乱码字符输出了
现在再开启compilerOptions.downlevelIteration
, 此时typescript处理for...of
时会通过辅助方法调用数组对象的Symbol.iterator
属性做一些额外的检查和处理, 就能正常输出了
此时生成的js代码:
"use strict";
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var e_1, _a;
var text = "(\uD83D\uDE1C)";
try {
for (var text_1 = __values(text), text_1_1 = text_1.next(); !text_1_1.done; text_1_1 = text_1.next()) {
var c = text_1_1.value;
console.log(c);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (text_1_1 && !text_1_1.done && (_a = text_1.return)) _a.call(text_1);
}
finally { if (e_1) throw e_1.error; }
}
更具体的说明看官网: https://www.typescriptlang.org/tsconfig#downlevelIteration
compilerOptions.importHelpers
上面介绍了compilerOptions.downlevelIteration
选项, 开启后会对for...of
之类的迭代语法糖进行downleveling
; typescript进行downleveling时, 会生成一些辅助方法, 默认情况下, 这些辅助代码是会直接插入到文件中对应的位置的, 这会生成的javascript存在重复的辅助方法从而造成代码文件体积过大的问题.
开启compilerOptions.importHelpers
后, 不在插入具体的辅助方法的代码到对应的位置, 而是通过模块导入来引用typescript的辅助方法
看这段typescript
export const foo = () => {
const text = `(😜)`
for (const c of text) {
console.log(c)
}
}
没开启compilerOptions.importHelpers
时, 生成的javascript:
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
export var foo = function () {
var e_1, _a;
var text = "(\uD83D\uDE1C)";
try {
for (var text_1 = __values(text), text_1_1 = text_1.next(); !text_1_1.done; text_1_1 = text_1.next()) {
var c = text_1_1.value;
console.log(c);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (text_1_1 && !text_1_1.done && (_a = text_1.return)) _a.call(text_1);
}
finally { if (e_1) throw e_1.error; }
}
};
开启compilerOptions.importHelpers
后, 生成的javascript:
import { __values } from "tslib";
export var foo = function () {
var e_1, _a;
var text = "(\uD83D\uDE1C)";
try {
for (var text_1 = __values(text), text_1_1 = text_1.next(); !text_1_1.done; text_1_1 = text_1.next()) {
var c = text_1_1.value;
console.log(c);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (text_1_1 && !text_1_1.done && (_a = text_1.return)) _a.call(text_1);
}
finally { if (e_1) throw e_1.error; }
}
};
compilerOptions.strict
这个strict
相关标志位的一个总开关, 设置为true
会启用全部compilerOptions.strict
开头的选项和其他相关的选项, 如compilerOptions.strictNullChecks
, compilerOptions.strictPropertyInitialization
, compilerOptions.noImplicitAny
…
开启此选项后, 依然可以单独关闭某个具体的以compilerOptions.strict
开头的选项
compilerOptions.strictBindCallApply
开启此选项后, 调用函数对象的bind
call
和apply
方法时typescript会执行参数类型检查确保参数类型兼容
const foo = (a: string, b: number) => {
console.log(a, b);
}
// 开启 strictBindCallApply 后,会报错
// Argument of type 'number' is not assignable to parameter of type 'string'.
foo.call(undefined, 1, 2)
compilerOptions.strictFunctionTypes
开启此选项会启用严格的函数类型检查, 直接看示例:
const foo = (a: string) => {
console.log(a);
}
interface Bar {
(a: string | string[]): void;
}
// 开启 compilerOptions.strictFunctionTypes, 报错
// Type '(a: string) => void' is not assignable to type 'Bar'.
const bar:Bar = foo;
修改成:
const foo = (a: string) => {
console.log(a);
}
interface Bar {
(a: string): void;
}
const bar:Bar = foo;
才能通过编译
compilerOptions.strictNullChecks
开启此选项让typescript执行严格的null检查
const foo: string|null|undefined = undefined;
// 不开启 compilerOptions.strictNullChecks ,不会有编译时错误,但是运行时会出现异常(Cannot read properties of undefined )
// 开启 compilerOptions.strictNullChecks,会出现编译时错误(Object is possibly 'undefined')
console.log(foo.length)
compilerOptions.strictPropertyInitialization
开启此选项让typescript严格的对象属性初始化检查
开启后这段代码会出现编译时错误:
class Foo {
// Property 'foo' has no initializer and is not definitely assigned in the constructor.
foo: string;
}
改成
class Foo {
foo = 'foo';
}
或者
class Foo {
foo: string;
constructor() {
this.foo = '';
}
}
或者在属性后加感叹号进行非空断言(non-null assertion)
class Foo {
foo!: string;
}
方能编译通过
compilerOptions.noImplicitAny
这个配置还是比较好理解的, 就是开启此选项后, 如果你声明一个没有标注类型的变量, 编译器会会给你一个编译时错误(Parameter 'arg' implicitly has an 'any' type.
)
给foo
函数参数arg
标注一个string
类型可以消除这个错误
const foo = (arg: string) => { console.log(arg) };
foo('hello');
另外要注意如果开启了compilerOptions.strict
选项, 那么这个选项默认就会处于开启状态, 除非手动将这个选项配置为false
compilerOptions.noImplicitOverride
typescript 4.3中才引入的配置
这个选项从名字上也是比较好理解的; 开启此选项保证子类重写基类的方法时, 必须在方法前加上override
关键词
class BillBuilder {
build() {}
}
class MonthBillBuilder extends BillBuilder {
// 开启 compilerOptions.noImplicitOverride 后,重写 build 方法必须显示加上 override 关键词,否则编译器会报错:
// This member must have an 'override' modifier because it overrides a member in the base class 'BillBuilder'.
build() {
console.log('Monthly bill')
}
}
正确的写法:
class BillBuilder {
build() {}
}
class MonthBillBuilder extends BillBuilder {
override build() {
console.log('Monthly bill')
}
}
compilerOptions.noImplicitReturns
开启这个选项保证编译时所有条件分支都返回一致的类型, 比如说一个if分支下返回了一个string类型, 但是其他分支没有进行return, 那么tsc会给出一个编译时错误(Not all code paths return a value.(7030)
)
正确的写法1:
const hello = (log = false) => {
if (log) {
const text = 'hello';
console.log(text);
return text
}
return '';
}
正确的写法2:
const hello = (log = false): string | void => {
if (log) {
const text = 'hello';
console.log(text);
return text
}
}
compilerOptions.noImplicitThis
开启这个选项后, typescript将禁止调用any类型的this
错误示例:
function Color() {
// 开启了ompilerOptions.noImplicitThis的情况下,下面的三行代码会出现编译时错误
// 'this' implicitly has type 'any' because it does not have a type annotation.
this.r = 255;
this.g = 255;
this.b = 255;
}
// 如果开启了 compilerOptions.noImplicitAny , 那么这一行也是会报编译时错误的:
// 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
const c = new Color();
console.log(c.r, c.r, c.b);
正确的写法:
class Color {
r = 255;
g = 255;
b = 255;
}
const c = new Color();
console.log(c.r, c.r, c.b);
compilerOptions.noPropertyAccessFromIndexSignature
这个配置选项typescript4.2中才引入
从名称入手来理解这个配置, no property access from index signature
, 就是说开启后禁止通过访问常规属性的方法来访问index signature
声明的属性
常规属性通过在对象后加一个.
即可访问如obj.title
什么是index signature
? 直译过来是索引签名, 索引签名语法一般用来声明接口或类中的未知属性的, index signature
的示例:
// 标注为IFoo类型的对象可以添加任意的字符串键值对
interface IFoo {
[key: string]: string;
}
const foo: IFoo = {
bar: '1'
}
console.log(foo['bar']);
// 输出: 1
开启compilerOptions.noPropertyAccessFromIndexSignature
后的一个错误示例:
class Color {
r = 255;
g = 255;
b = 255;
[key: string]: string | number;
}
const c = new Color();
console.log(c.r, c.g, c.b);
// 开启 compilerOptions.noPropertyAccessFromIndexSignature 的情况下,会有编译时错误:
// Property 'foo' comes from an index signature, so it must be accessed with ['foo'].
console.log(c.foo);
正确的方式应该是通过c['foo']
访问Color
对象c
上的foo
属性
这个选项的动机是什么?
不开启compilerOptions.noPropertyAccessFromIndexSignature
直接通过传统的.
符号来访问索引签名属性, 一不小心非常容易造成运行时出现经典的read property of undefined
异常, 比如上面的示例如果直接调用c.foo.toString()
, 即使开启了compilerOptions.strictNullChecks
编译仍能通过, 但是运行就会发生异常; 当然也可以显示标注索引签名属性为可空类型配置compilerOptions.strictNullChecks
来达到相同的目的
class Color {
r = 255;
g = 255;
b = 255;
[key: string]: string | number | undefined;
}
const c = new Color();
console.log(c.r, c.g, c.b);
// 关闭 compilerOptions.noPropertyAccessFromIndexSignature,开启 compilerOptions.strictNullChecks
// 仍然可以获得编译时的对象属性可能为空的错误:
// Object is possibly 'undefined'.
console.log(c.foo.toString());
compilerOptions.noUncheckedIndexedAccess
这个配置选项typescript4.1中才引入
开启这个选项, typescript自动给索引签名语法声明的属性补上一个undefined
类型; 上面介绍compilerOptions.noPropertyAccessFromIndexSignature
时提到可以自己手动给索引签名语法声明的属性加上undefined
类型标注达到和开启compilerOptions.noPropertyAccessFromIndexSignature
相同的目的
看上面的图片中虽然没有显示给索引签名属性标注undefined
, 但是鼠标悬浮到c.foo
上时typescript自动给它加上了undefined
compilerOptions.noUnusedLocals
又是一个很好理解的配置选项, 开启这个选项, 当typescript发现未使用的局部变量时, 会给出一个编译时错误('<propertyName>' is declared but its value is never read.(6133)
)
const sayHello = () => {
// 开启 compilerOptions.noUnusedLocals 后,typescript编译时会报错
// 'text' is declared but its value is never read.
const text = 'hello';
console.log('hello');
}
✅正确的做法:
const sayHello = () => {
const text = 'hello';
console.log(text);
}
compilerOptions.noUnusedParameters
和上面的compilerOptions.noUnusedLocals
, 不同之处在于局部变量变成了函数参数
❌错误示例:
// 开启 compilerOptions.noUnusedParameters 后,typescript编译时会报错
// 'text' is declared but its value is never read.
const sayHello = (text: string) => {
console.log('hello');
}
✅正确的写法:
// 开启 compilerOptions.noUnusedParameters 后,typescript编译时会报错
// 'text' is declared but its value is never read.
const sayHello = (text: string) => {
console.log(text);
}
compilerOptions.useUnknownInCatchVariables
这个配置选项typescript4.4中才引入
开启这个选项typescript会将catch
语法块中的err
变量当做unknown来处理, 不开启此选项时, err
变量是被当做any
类型来处理的, 这很容易造成经典的read property of undefined
运行时异常
一个简单的示例, 不开启compilerOptions.useUnknownInCatchVariables
选项运行时才能发布异常
开启了compilerOptions.useUnknownInCatchVariables
选项, 编译时立即发现问题
小结
本文主要介绍了typescript中类型检查相关的配置, typescript还有其他不少配置的, 官网都有详细的文档
我对typescript一些看法
我是18年大二阶段开始接触前端相关的编程语言的, 那个时候typescript还没有这么流行, 接触使用学习时还是以js为主的; 此前主主要使用的语言的是C/C++
C#
这些强类型语言, 对js存在强烈的抵触甚至厌恶, 然后接触到angular时发现它的整个生态都是构建在typescript之上的, 终于遇到了救星; 虽然那个时候刚刚接触, angular用起来也是一知半解的, 常常被typescript中如何使用js库
之类的小白问题支配, 但是相比要我写没有类型注解的javascript, 那时的我依然选择慢慢摸索解决typescript中如何使用js库
这样的小白问题, 解决这些问题过程中, 我对js也有了更深入的理解, 慢慢的我甚至可以脱离typescript学会写javascript了😂typescript的定位是javascript的超集, 与我而言, typescript确实我在javascript上的老师, 没有typescript, 那个时候的我可能已经放弃学习javascript了, 就像叫不醒一个装睡的人一样, 我们永远也学不会一门自己不想学设置讨厌的的编程语言; 也不是说没有typescript就永远都不会对javascript产生兴趣, 而是产生兴趣的时间会延后到不知何时;
typescript是一个良师益友, 不敢想象没有typescript的世界将会是怎样😇或许也并不会怎样, 只不过少了一个爱写前端的靓仔而已🌚
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/29733.html