Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Typescript 严格模式有多严格?

Typescript 严格模式有多严格?

作者头像
前端劝退师
发布于 2019-11-07 07:14:19
发布于 2019-11-07 07:14:19
2.1K00
代码可运行
举报
文章被收录于专栏:前端劝退师前端劝退师
运行总次数:0
代码可运行

前言

"use strict"指令在JavaScript 1.8.5 (ECMAScript5)中新增。

至今,前端 er 们基本都默认开启严格模式敲代码。

那么,你知道Typescript其实也有属于自己的严格模式吗?

1.Typescript严格模式规则

Typescript严格模式设置为on时,它将使用strict族下的严格类型规则对项目中的所有文件进行代码验证。规则是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
不允许变量或函数参数具有隐式any类型。

2.noImplicitAny

此规则不允许变量或函数参数具有隐式any类型。请看以下示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Javascript/Typescript 非严格模式
function extractIds (list) {
  return list.map(member => member.id)
}

上述例子没有对list进行类型限制,map循环了item的形参member。而在Typescript严格模式下,会出现以下报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript 严格模式
function extractIds (list) {
  //              ❌ ^^^^
  //                 Parameter 'list' implicitly
  //                 has an 'any' type. ts(7006)
  return list.map(member => member.id)
  //           ❌ ^^^^^^
  //              Parameter 'member' implicitly
  //              has an 'any' type. ts(7006)
}

正确写法应是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript 严格模式
interface Member {
  id: number
  name: string
}

function extractIds (list: Member[]) {
  return list.map(member => member.id)
}

1.1 浏览器自带事件该如何处理?

浏览器自带事件,比如e.preventDefault(),是阻止浏览器默认行为的关键代码。

这在Typescript严格模式下是会报错的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript 严格模式
function onChangeCheckbox (e) {
  //                    ❌ ^
  //                       Parameter 'e' implicitly
  //                       has an 'any' type. ts(7006)
  e.preventDefault()
  const value = e.target.checked
  validateCheckbox(value)
}

若需要正常使用这类Web API,就需要在全局定义扩展。比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript 严格模式
interface ChangeCheckboxEvent extends MouseEvent {
  target: HTMLInputElement
}

function onChangeCheckbox (e: ChangeCheckboxEvent) {
  e.preventDefault()
  const value = e.target.checked
  validateCheckbox(value)
}

1.2 第三方库也需定义好类型

请注意,如果导入了非Typescript库,这也会引发错误,因为导入的库的类型是any

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript 严格模式
import { Vector } from 'sylvester'
//                  ❌ ^^^^^^^^^^^
//                     Could not find a declaration file
//                     for module 'sylvester'.
//                     'sylvester' implicitly has an 'any' type.
//                     Try `npm install @types/sylvester`
//                     if it exists or add a new declaration (.d.ts)
//                     file containing `declare module 'sylvester';`
//                     ts(7016)

这可能是项目重构Typescript版的一大麻烦,需要专门定义第三方库接口类型

3.noImplicitThis

此规则不允许this上下文隐式定义。请看以下示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Javascript/Typescript 非严格模式
function uppercaseLabel () {
  return this.label.toUpperCase()
}

const config = {
  label: 'foo-config',
  uppercaseLabel
}

config.uppercaseLabel()
// FOO-CONFIG

在非严格模式下,this指向config对象。this.label只需检索config.label

但是,this在函数上进行引用可能是不明确的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript严格模式
function uppercaseLabel () {
  return this.label.toUpperCase()
  //  ❌ ^^^^
  //     'this' implicitly has type 'any'
  //     because it does not have a type annotation. ts(2683)
}

如果单独执行this.label.toUpperCase(),则会因为this上下文config不再存在而报错,因为label未定义。

解决该问题的一种方法是避免this在没有上下文的情况下使用函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript严格模式
const config = {
  label: 'foo-config',
  uppercaseLabel () {
    return this.label.toUpperCase()
  }
}

更好的方法是编写接口,定义所有类型,而不是Typescript来推断:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript严格模式
interface MyConfig {
  label: string
  uppercaseLabel: (params: void) => string
}

const config: MyConfig = {
  label: 'foo-config',
  uppercaseLabel () {
    return this.label.toUpperCase()
  }
}

4.strictNullChecks

此规则不允许出现nullundefined的可能性。请看以下示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript 非严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
}

Typescript非严格模式下,这样写不会有任何问题。但严格模式会非给你搞出点幺蛾子:

“你这样不行,万一find没有匹配到任何值呢?”:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
  //  ❌ ^^^^^^^
  //     Object is possibly 'undefined'. ts(2532)
}

“我星星你个星星!”

于是你会将改成以下模样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  if (typeof article === 'undefined') {
    throw new Error(`Could not find an article with id: ${id}.`)
  }

  return article.meta
}

“真香!”

5.strictPropertyInitialization

此规则将验证构造函数内部初始化前后已定义的属性。

必须要确保每个实例的属性都有初始值,可以在构造函数里或者属性定义时赋值。

strictPropertyInitialization,这臭长的命名像极了React源码里的众多任性属性)

请看以下示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript非严格模式
class User {
  username: string;
}

const user = new User();

const username = user.username.toLowerCase();

如果启用严格模式,类型检查器将进一步报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class User {
  username: string;
  //    ❌  ^^^^^^
  //     Property 'username' has no initializer
  //     and is not definitely assigned in the constructor
}

const user = new User();
/
const username = user.username.toLowerCase();
 //                 ❌         ^^^^^^^^^^^^
//          TypeError: Cannot read property 'toLowerCase' of undefined

解决方案有四种。

方案#1:允许undefined

username属性定义提供一个undefined类型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class User {
  username: string | undefined;
}

const user = new User();

username属性可以为string | undefined类型,但这样写,需要在使用时确保值为string类型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const username = typeof user.username === "string"
  ? user.username.toLowerCase()
  : "n/a";

这也太不Typescript了。

方案#2:属性值显式初始化

这个方法有点笨,却挺有效:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class User {
  username = "n/a";
}

const user = new User();

// OK
const username = user.username.toLowerCase();

方案#3:在构造函数中赋值

最有用的解决方案是向username构造函数添加参数,然后将其分配给username属性。

这样,无论何时new User(),都必须提供默认值作为参数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class User {
  username: string;

  constructor(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

还可以通过public修饰符进一步简化:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class User {
  constructor(public username: string) {}
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

方案#4:显式赋值断言

在某些场景下,属性会被间接地初始化(使用辅助方法或依赖注入库)。

这种情况下,你可以在属性上使用显式赋值断言来帮助类型系统识别类型。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class User {
  username!: string;

  constructor(username: string) {
    this.initialize(username);
  }

  private initialize(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

通过向该username属性添加一个明确的赋值断言,我们告诉类型检查器:username,即使它自己无法检测到该属性,也可以期望该属性被初始化。

6.strictBindCallApply

此规则将对bind, call, apply更严格地检测类型。

啥意思?请看以下示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// JavaScript
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2])
// 3

在你不记得参数类型时,非严格模式下不会校验参数类型和数量,运行代码时,Typescript和环境(可能是浏览器)都不会引发错误:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript非严格模式
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2, 3])
// 还是...3?

Typescript严格模式下,这是不被允许的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript严格模式
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2, 3])
//           ❌ ^^^^^^^^^
//              Argument of type '[number, number, number]' is not
//              assignable to parameter of type '[number, number]'.
//                Types of property 'length' are incompatible.
//                  Type '3' is not assignable to type '2'. ts(2345)

那怎么办?“...”扩展运算符和reduce老友来相救

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Typescript严格模式
function sum (...args: number[]) {
  return args.reduce<number>((total, num) => total + num, 0)
}

sum.apply(null, [1, 2, 3])
// 6

7. strictFunctionTypes

该规则将检查并限制函数类型参数是抗变(contravariantly)而非双变(bivariantly,即协变或抗变)的。

初看,内心 OS:“这什么玩意儿?”,这里有篇介绍:

协变(covariance)和抗变(contravariance)是什么?[1]

协变和逆变维基上写的很复杂,但是总结起来原理其实就一个。

  • 子类型可以隐性的转换为父类型

说个最容易理解的例子,intfloat两个类型的关系可以写成下面这样。intfloat:也就是说intfloat的子类型。

这一更严格的检查应用于除方法或构造函数声明以外的所有函数类型。方法被专门排除在外是为了确保带泛型的类和接口(如 Array )总体上仍然保持协变。

请看下面这个AnimalDogCat的父类型的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;
f1 = f2;  // 启用 --strictFunctionTypes 时错误
f2 = f1;  // 正确
f2 = f3;  // 错误
  1. 第一个赋值语句在默认的类型检查模式中是允许的,但是在严格函数类型模式下会被标记错误。
  2. 而严格函数类型模式将它标记为错误,因为它不能 被证明合理。
  3. 任何一种模式中,第三个赋值都是错误的,因为它 永远不合理。

用另一种方式来描述这个例子则是,默认类型检查模式中T在类型(x: T) => void是 双变的,但在严格函数类型模式中T是 抗变的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interface Comparer<T> {
    compare: (a: T, b: T) => number;
}

declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer;  // 错误
dogComparer = animalComparer;  // 正确

写到此处,逼死了一个菜鸡前端。

总结&参考

参考文章:

  1. How strict is Typescript’s strict mode?[2]
  2. 应该怎么理解编程语言中的协变逆变?[3]
  3. TypeScript 严格函数类型[4]

在面试的过程中,常被问到为什么TypescriptJavaScript好用?

从这些严格模式规则,你就可以一窥当中的奥秘,今日开严格,他日 Bug 秒甩锅,噢耶。

参考资料

[1]

协变(covariance)和抗变(contravariance)是什么?:https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance

[2]

How strict is Typescript’s strict mode?:https://medium.com/swlh/how-strict-is-typescripts-strict-mode-f36a4d1a948a

[3]

应该怎么理解编程语言中的协变逆变?:https://www.zhihu.com/question/38861374

[4]

TypeScript 严格函数类型:https://www.tslang.cn/docs/release-notes/typescript-2.6.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端劝退师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【TypeScript 4.5】002-第 2 章 TypeScript 入门
关闭严格模式,类型隐式推断为 any 类型,也就回到了普通的 js 代码的效果了,与是否使用 ts 无区别!我们可以 strict 严格模式,我们呢也可以将 noImplicitAny 设置为 true。
訾博ZiBo
2025/01/06
1190
【TypeScript 4.5】002-第 2 章 TypeScript 入门
TypeScript 类型体操 - 基础操作
类型即 number、boolean、string 等基础类型和 Object、Function 等复合类型,它们是编程语言提供的对不同内容的抽象:
Cellinlab
2023/05/17
1.9K0
TypeScript 非空断言
使用这种方案,问题是解决了。但有没有更简单的方式呢?答案是有的,就是使用 TypeScript 2.0 提供的非空断言操作符:
阿宝哥
2020/04/08
20.7K0
TypeScript 快速入门
由于这种强弱类型之分根本不是某一个权威机构的定义,一般描述强类型有更强的类型约束,而弱类型中几乎没有什么约束。
用户3045442
2020/08/06
1.6K0
TypeScript 快速入门
前端应该掌握的Typescript基础知识
js 是一门动态弱类型语言, 我门可以随意的给变量赋不同类型的值 ts 是拥有类型检查系统的 javascript 超集, 提供了对 es6 的支持, 可以编译成纯 javascript,运行在任何浏览器上。 TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。
前端老鸟
2022/03/07
6420
TypeScript 快速入门(基础篇)
TypeScript 是一门由微软开发的免费开源的编程语言。它是 JavaScript 的一个超集,TypeScript 在 JavaScript 的基础上添加了可选的静态类型和基于类的面向对象编程。
程序员海军
2021/10/11
1K0
TypeScript 快速入门(基础篇)
TypeScript基础知识
TypeScript是JavaScript的一个超集,支持ECMAScript6标准。
岳泽以
2022/11/22
2.3K0
TypeScript基础知识
一份不可多得的TypeScript系统入门整理
函数声明(Function Declaration)和函数表达式(Function Expression)
@超人
2021/07/29
1.8K0
一份不可多得的TypeScript系统入门整理
TypeScript 2.6 来了!
TypeScript 2.6 在万圣节出现啦!不过不用怕,我们在这个版本中准备了很多好玩的东西。
疯狂的技术宅
2019/03/27
1.2K0
typescript[0x01]--基础数据类型
楼上这句话后半部分听不懂没有关系,下面跟ataola一起通过一些具体实例和思考,来一起学习一下typescript吧!
江涛学编程
2020/06/19
5570
TypeScript
TypeScript中的Object类型并不单指普通的对象类型,而是泛指非原始类型,也就是对象,数组和函数
conanma
2021/10/28
1.8K0
深入浅出 TypeScript
本文是阅读小册 「《深入浅出 TypeScript》」 的阅读笔记,对TypeScript感兴趣的同学请继续阅读吧。
chuckQu
2022/08/19
2.9K0
TypeScript学习笔记(三)—— 编译选项、声明文件
compilerOptions ⽀持很多选项,常⻅的有 baseUrl 、 target 、 moduleResolution 和 lib 等。 compilerOptions 每个选项的详细说明如下:
张果
2022/10/04
2.6K0
TypeScript学习笔记(三)—— 编译选项、声明文件
TypeScript入门秘籍:快速掌握静态类型编程
TypeScript是一种静态类型的JavaScript超集,它添加了可选的类型注解,使得代码更加健壮、易于维护。无论你是初学者还是有一定编程经验的开发者,这篇博客将带你快速入门TypeScript。
Front_Yue
2024/09/08
1570
TypeScript入门秘籍:快速掌握静态类型编程
TypeScript防脱发级入门——模块化
在TS中使用模块化是比较简单的,并且我们在导入的时候是不需要自己写import的,可以利用TS的智能提示,如下:
程序员法医
2022/08/11
5750
TypeScript防脱发级入门——模块化
JavaScript(六)
ECMAScript 中的函数实际上是对象,每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。函数通常是使用函数声明语法定义的,如下:
1ess
2021/10/29
3770
10个写TypeScript代码的坏习惯
近几年 TypeScript 和 JavaScript 一直在稳步发展。我们在过去写代码时养成了一些习惯,而有些习惯却没有什么意义。以下是我们都应该改正的 10 个坏习惯。
疯狂的技术宅
2021/02/25
6940
10个写TypeScript代码的坏习惯
Typescript基本类型
自从ES2015中引入了Symbol,ES2020引入了BigInt,在js中共包含了7种基本类型,分别是String,Number,Boolean,undefind,null,Symbol,BigInt。同时,这些基本类型在ts中也相应的包含了这些基本类型。
前端进击者
2021/07/27
4320
【项目总结】TypeScript基础
TypeScript解决了JavaScript存在的很多设计缺陷,尤其是关于类型检测的。从开发者长远的角度来看,学习TypeScript有助于前端程序员培养 类型思维,这种思维方式对于完成大型项目尤为重要。个人用起来感觉很像Java。
客怎眠qvq
2022/11/01
6660
【项目总结】TypeScript基础
TypeScript 终极初学者指南
大家好,我是 ConardLi,在过去的几年里 TypeScript 变得越来越流行,现在许多工作都要求开发人员了解 TypeScript,各大厂的大型项目基本都要求使用 TypeScript 编写。
ConardLi
2022/04/08
6.9K0
TypeScript 终极初学者指南
相关推荐
【TypeScript 4.5】002-第 2 章 TypeScript 入门
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验