Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【TypeScript 演化史 -- 11】泛型参数默认类型 和 新的 --strict 编译选项 #177

Open
husky-dot opened this issue Jan 4, 2020 · 0 comments

Comments

@husky-dot
Copy link
Owner

作者:Marius Schulz
译者:前端小智
来源:https://mariusschulz.com/

TypeScript 2.3 增加了对声明泛型参数默认类型的支持,允许为泛型类型中的类型参数指定默认类型。

接下来看看如何通过泛型参数默认将以下React组件从 JS (和JSX)迁移到 TypeScript (和TSX):

class Greeting extends React.Component {
  render() {
    return <span>Hello, {this.props.name}!</span>;
  }
}

为组件类创建类型定义

咱们先从为 Component 类创建类型定义开始。每个基于类的 React 组件都有两个属性: propsstate,类型定义结构大致如下:

declare namespace React {
  class Component {
    props: any;
    state: any;
  }
}

注意,这个是大大简化的示例,因为咱们是为了演示泛型类型参数及其默认值的内容。

现在就可以通过继承来调用上面定义的 Component:

class Greeting extends React.Component {
  render() {
    return <span>Hello, {this.props.name}!</span>
  }
}

咱们可以如下方式创建组件的实例:

<Greeting name="world" />

渲染上面组件会生成以下 HTML:

<span>Hello, World!</span>

nice,继续。

使用泛型类型定义 Props 和 State

虽然上面的示例编译和运行得很好,但是咱们的 Component 类型定义不是很精确。因为咱们将 propsstate 类型设置为 any,所以 TypeScript 编译器也帮不上什么忙。

咱们得更具体一点,通过两种泛型类型: PropsState,这样就可以准确地描述 propsstate 属性的结构。

declare namespace React {
  class Component <Props, State> {
    props: Props;
    state: State;
  }
}

接着创建一个GreetingProps类型,该类型定义一个字符串类型 name 的属性,并将其作为Props类型参数的类型参数传递:

type GreetingProps = { name: string };

class Greeting extends React.Component<GreetingProps, any> {
  render() {
    return <span>Hello, {this.props.name}!</span>;
  }
}
  1. GreetingProps 是类型参数Props的类型参数

  2. 类似地,any是类型参数 State 的类型参数

有了这些类型,咱们的组件得到更好的类型检查和自动提示:

但是,现在使用 React.Component 类时就必需供两种类型。咱们开着的初始代码示例就不在正确地进行类型检查:

// Error: 泛型类型 Component<Props, State>
// 需要 2 个类型参数。
class Greeting extends React.Component {
  render() {
    return <span>Hello, {this.props.name}!</span>;
  }
}

如果咱们不想指定像GreetingProps这样的类型,可以通过为PropsState类型参数提供any类型来修正代码:

class Greeting extends React.Component<any, any> {
  render() {
    return <span>Hello, {this.props.name}!</span>;
  }
}

这种方法可以让编译器通过,但咱们还有更优雅的做法:泛型参数默认类型

泛型参数默认类型

从 TypeScript 2.3 开始,咱们可以为每个泛型类型参数添加一个默认类型。在下面的例子中,如果没有显式地给出类型参数,那么 PropsState 都都是 any 类型:

declare namespace React {
  class Component<Props = any, State = any> {
    props: Props;
    state: State;
  }
}

现在,咱们就可以不用指定泛型类型就可以通过编译器的检查:

class Greeting extends React.Component {
  render() {
    return <span>Hello, {this.props.name}!</span>;
  }
}

当然,咱们仍然可以显式地为Props类型参数提供类型并覆盖默认的any类型,如下所示:

type GreetingProps = { name: string };

class Greeting extends React.Component<GreetingProps, any> {
  render() {
    return <span>Hello, {this.props.name}!</span>;
  }
}

这两个类型参数现在都有一个默认类型,所以它们是可选的,咱们可以仅为Props指定显式的类型参数:

type GreetingProps = { name: string };

class Greeting extends React.Component<GreetingProps> {
  render() {
    return <span>Hello, {this.props.name}!</span>;
  }
}

注意,咱们只提供了一个类型参数。但是,被省略可选类型参数前一个必须要指定类型,否则不能省略。

其它事例

在上一篇中关于 TypeScript 2.2 中混合类的文章中,咱们最初声明了以下两个类型别名:

type Constructor<T> = new (...args: any[]) => T;
type Constructable = Constructor<{}>;

Constructable类型纯粹是语法糖。它可以代替 Constructor<{}> 类型,这样就不必每次都要写泛型类型参数。使用泛型参数默认值,就可以完全去掉附加的可构造类型,并将{}设置为默认类型

type Constructor<T = {}> = new (...args: any[]) => T;

语法稍微复杂一些,但是生成的代码更简洁,Good。

新的 --strict 主要编译选项

TypeScript 2.3 引入了一个新的 --strict 编译器选项,它支持许多与更严格的类型检查相关的其他编译器选项。

TypeScript 加入的新检查项为了避免不兼容现有项目通常都是默认关闭的。虽然避免不兼容是好事,但这个策略的一个弊端则是使配置最高类型安全越来越复杂,这么做每次 TypeScript 版本发布时都需要显示地加入新选项。有了--strict编译选项,就可以选择最高级别的类型安全(了解随着更新版本的编译器增加了增强的类型检查特性可能会报新的错误)。

新的--strict编译器选项包含了一些建议配置的类型检查选项。具体来说,指定--strict相当于是指定了以下所有选项(未来还可能包括更多选项):

  • --strictNullChecks
  • --noImplicitAny
  • --noImplicitThis
  • --alwaysStrict

未来的 TypeScript 版本可能会在这个集合中添加额外的类型检查选项。这意味着咱们不需要监控每个 TypeScript 版本来获得应该在项目中启用的新严格性选项。如果向上述选项集添加了新选项,则在升级项目的 TypeScript 版本后,它们将自动激活。

--strict 编译选项会为以上列出的编译器选项设置默认值。这意味着还可以单独控制这些选项。比如:

--strict --noImplicitThis false 

或者在tsconfig.json 文件指定:

{
  "strict": true,
  "alwaysStrict": false
}

这将是开启除--noImplicitThis编译选项以外的所有严格检查选项。使用这个方式可以表述除某些明确列出的项以外的所有严格检查项。换句话说,现在可以在默认最高级别的类型安全下排除部分检查。

改进的 --init 输出

除了默认的--strict设置外,tsc --init还改进了输出。tsc --init默认生成的tsconfig.json文件现在包含了一些带描述的被注释掉的常用编译器选项. 你可以去掉相关选项的注释来获得期望的结果。我们希望新的输出能简化新项目的配置并且随着项目成长保持配置文件的可读性。

通过 tsc --init 编译器可以为构建一个配置文件:

$ tsc --init
message TS6071: Successfully created a tsconfig.json file.

运行此命令后,会当前工作目录中生成一个tsconfig.json文件,生成的配置如下所示:

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'. */
    // "lib": [],                             /* Specify library files to be included in the compilation:  */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true                            /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */

    /* Source Map Options */
    // "sourceRoot": "./",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "./",                       /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
  }
}

注意 --strict 是默认启用的。这意味着在启动一个新的TypeScript项目时,自动进入默认模式。

--checkJS 选项下 .js 文件中的错误

即便使用了--allowJs,TypeScript 编译器默认不会报 .js 文件中的任何错误。TypeScript 2.3 中使用--checkJs选项,.js文件中的类型检查错误也可以被报出.

你可以通过为它们添加// @ts-nocheck注释来跳过对某些文件的检查,反过来你也可以选择通过添加// @ts-check注释只检查一些.js文件而不需要设置--checkJs编译选项。你也可以通过添加// @ts-ignore到特定行的一行前来忽略这一行的错误.

.js文件仍然会被检查确保只有标准的 ECMAScript 特性,类型标注仅在.ts文件中被允许,在.js中会被标记为错误。JSDoc注释可以用来为你的 JS 代码添加某些类型信息,


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:
https://mariusschulz.com/blog/generic-parameter-defaults-in-typescript
https://mariusschulz.com/blog/the-strict-compiler-option-in-typescript


交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq449245884/xiaozhi

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant