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

Typescript 严格模式有多严格?

作者头像
用户1462769
发布于 2019-11-18 14:27:04
发布于 2019-11-18 14:27:04
3.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 秒甩锅,噢耶。

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

本文分享自 全栈者 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
react-native 打包流程
原生应用会提供js运行环境,因此实际上还是要将应用代码打包为一个js文件,实际运行时也是在js解释器的帮助下执行,也因此与原生应用存在一定的性能差距。 回到命令行执行以下命令:
conanma
2022/01/08
6160
React Native App设置&Android版发布
React Native系列 《逻辑性最强的React Native环境搭建与调试》 《ReactNative开发工具有这一篇足矣》 《解决React Native unable to load
磊哥
2018/05/08
1K0
React Native App设置&Android版发布
react native打包apk
前言 最近自己的react native app已经完成了,博主想将其打包成android apk运行在安卓端的手机上。然后遇到了一系列的问题。经过一段时间的踩坑,已经成功打包成apk文件并在手机端正常运行和显示。下面介绍一下react native打包成android apk的原理和步骤。 基本原理 react native程序在调试的时候,在虚拟机端是通过连接本地node服务来获取js文件,所以可以实现热加载。但是要打包成apk时,需要将这些js文件打包成bundle包,当做android的运行资源。放
十里桃花舞丶
2018/05/17
2.2K0
React-Native Android打包
my-release-key.keystore 为密钥库文件名,在android/app文件夹下。 my-key-alias 为生成秘钥的时候设置的别名。
憧憬博客
2020/07/21
7850
React Native APP签名打包release版本APK
首先React Native开发的APP是无法通过Android Studio进行打包的,因为AS打包的APK,也是和debug版本一样,需要进行依托localhost:8081服务运行。所以我们必须采用官方推荐的打包方式才可以。
做全栈攻城狮
2018/12/20
1.4K0
React Native之打包
用React Native开发好APP之后,如何将APP发布到市场以供用户使用呢?不管是Android还是ios等原生app走的都是:签名打包—>发布到各store这两大步骤。本文将对Android和ios两大平台打包过程做一个简单的介绍。 Android平台打包 在Android原生的app开发打包过程中,主要有两种发布方式:一种是借助于命令行操作,另外一种是借助于Android Studio进行发布。 Android Studio打包 借助Android studio进行打包的,一次点击build->g
xiangzhihong
2018/02/06
2K0
React Native之打包
React Native 混合开发(Android篇)
在React Native的应用场景中,有时候一个APP只有部分页面是由React Native实现的,比如:我们常用的携程App,它的首页下的很多模块都是由React Native实现的,这种开发模式被称为混合开发。
CrazyCodeBoy
2018/09/26
4.1K0
React-day6
搭建基本的开发环境 - 英文官网 搭建基本的开发环境 - 中文 这两篇文档对比着进行参考,进行相关的安装;
海仔
2021/05/06
1.4K0
Android APK 加固重新签名
通过 ./gradlew assembleRelease 命令打包,此时的apk没有加固,不符合安全需要
草帽lufei
2022/07/29
3.7K0
Android APK  加固重新签名
React Native发布APP之签名打包APK
React Native发布APP之签名打包APK ---- 用React Native开发好APP之后,如何将APP发布以供用户使用呢?一款APP的发布流程无外乎:签名打包—>发布到各store这两大步骤。本文将向大家分享如何签名打包一款React Native APP。 众所周知,Android要求所有的APP都需要进行数字签名后,才能够被安装到相应的设备上。签名打包一个Android APP已经是每一位Android开发者的家常便饭了。 那么如何签名打包一款用React Native开发的APP呢?
CrazyCodeBoy
2018/05/07
2.7K0
React Native发布APP之签名打包APK
7. 偷用Swiper简改
看这段代码应该就很清楚了,如果是android系统就渲染Pager如果是ios就使用横向的ScrollView,修改后的app首页如下:
MasterVin
2018/08/30
2K0
7. 偷用Swiper简改
从Android到React Native开发(四、打包流程解析和发布为Maven库)
 作为失踪人口,本篇是对前三篇React Native文章的番外补充,主要实现把React Native项目,打包为完整aar库发布到maven,提供库支持的功能,算是小众化的需求吧,不过通过本篇你可以了解:
GSYTech
2018/08/11
2.1K0
从Android到React Native开发(四、打包流程解析和发布为Maven库)
weex 踩坑笔记 【原创】
weex 踩坑笔记 Write By CS逍遥剑仙 我的主页: www.csxiaoyao.com GitHub: github.com/csxiaoyaojianxian Email: sunjianfeng@csxiaoyao.com QQ: 1724338257 目录导航 weex 踩坑笔记 1. 基本说明 2. weex-toolkit的使用 2.1 配置入口js文件 2.2 基本命令 2.3 开发调试方式 3. 集成SDK 3.1 集成
CS逍遥剑仙
2018/04/28
2.3K0
weex 踩坑笔记 【原创】
React Native应用部署/热更新-CodePush最新集成总结(新)
React Native应用部署/热更新-CodePush最新集成总结(新) ---- 更新说明: 此次博文更新适配了最新版的CodePush v1.17.0;添加了iOS的集成方式与调试技巧;添加了更为简洁的CodePush发布更新的方式以及进行了一些其他的优化。 React Native的出现为移动开发领域带来了两大革命性的创新: 整合了移动端APP的开发,不仅缩短了APP的开发时间,也提高了APP的开发效率。 为移动APP动态更新提供了基础。 本文将向大家分享React Natvie应用部署/
CrazyCodeBoy
2018/05/07
3.4K0
React Native应用部署/热更新-CodePush最新集成总结(新)
最火移动端跨平台方案盘点:React Native、weex、Flutter
跨平台一直是老生常谈的话题,cordova、ionic、react-native、weex、kotlin-native、flutter等跨平台框架的百花齐放,颇有一股推倒原生开发者的势头。
JackJiang
2018/08/13
7.4K0
最火移动端跨平台方案盘点:React Native、weex、Flutter
ReactNative windows下打包生成安卓apk
create-react-native-app 运行npm run eject后根目录会产生一个andriod目录和ios目录。里面就是运行打包的配置文件。 如果你是用react-native-cli 开发RN的应该一开始就有这俩目录。
mafeifan
2018/09/10
1.4K0
ReactNative windows下打包生成安卓apk
React Native bundle打离线包
react-native-cli 自带脚本可以打包 react-native bundle 命令
conanma
2022/03/09
2.8K0
React Native热更新方案
随着 React Native 的不断发展完善,越来越多的公司选择使用 React Native 替代 iOS/Android 进行部分业务线的开发,也有不少使用 Hybrid 技术的公司转向了 React Native 。虽然React Native在目前来说仍有不少的坑,不过对于以应用开发为主的App来说完全可以胜任。 概述 在iOS应用开发中,由于Apple严格的审核标准和低效率,iOS应用的发版速度极慢,这对于大多数团队来说是不能接受的,所以热更新对于iOS应用来说就显得尤其重要。而就在前不久,苹果
xiangzhihong
2018/02/06
9.6K0
React Native热更新方案
React Native应用部署/马甲包热更新-CodePush最新集成总结(2018年最新)
React Native支持大家用React Native技术开发APP,并打包生成一个APP。在动态更新方面React Native只是提供了动态更新的基础,对将应用部署到哪里,如何进行动态更新并没有支持的那么完善。好在微软开发了CodePush,填补React Native 应用在动态更新方面的空白。CodePush 是微软提供的一套用于热更新 React Native 和 Cordova 应用的服务。下面将向大家分享如何使用CodePush实时更新你的应用,后期会分享不采用CodePush,如何自己去实现React Native应用热更新。
jiang chen
2018/10/20
2.9K0
React Native应用部署/马甲包热更新-CodePush最新集成总结(2018年最新)
React Native发布APP之打包iOS应用
React Native发布APP之打包iOS应用 ---- 用React Native开发好APP之后,如何将APP发布以供用户使用呢?一款APP的发布流程无外乎:签名打包—>发布到各store这两大步骤。本文将向大家分享如何签名打包一款React Native APP。 在本文中我将为大家讲解如何打包和发布React Native iOS App。 第一步:导出js bundle包和图片资源 和打包React Native Android应用不同的是,我们无法通过命令一步进行导出React Native
CrazyCodeBoy
2018/05/07
2.9K0
React Native发布APP之打包iOS应用
相关推荐
react-native 打包流程
更多 >
LV.3
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验