TypeScript 2.7 记录
TypeScript 2.7版本记录
针对ts 2.7版本的特性作专门的实例,希望能加深理解。实例github地址 官方日志文档
增加常量声明的属性的支持(Constant-named properties)
对于常量,有更加智能的提示
const Foo = "Foo"; const Bar = "Bar"; let x = { // 2.7版本之前,x的类型为{[x:string]:string|number},现在为 {[Foo]: number;[Bar]: string;}} [Foo]: 100, [Bar]: "hello", }; let a = x[Foo]; // has type 'number' let b = x[Bar]; // has type 'string'
typescript 增加一种新的类型声明:unique symbols
,是 symbols的子类型,仅可通过调用 Symbol()或 Symbol.for()或由明确的类型注释生成
ES6
引入的 Symbol
机制,Symbol
是js的第七种数据类型,可以产生独一无二的值,可以用来保证每个属性的名字都是独一无二,从根本上防止属性名的冲突。结合ts,我们可以这样声明一个symbol,const Foo: unique symbol = Symbol()
unique symbol
类型必须由 const
关键字声明
// Error! 'Bar' isn't a constant. let Bar: unique symbol = Symbol();
引用赋值一个 unique symbol
类型的值时候使用 typeof
操作符
// let Baz = Foo // 这样的话 Baz 的类型为symbol let Baz: typeof Foo = Foo // 类型为unique symbol // 在class里面使用 `unique symbol` 定义类的静态属性(不能用在类属性) 时, // 需要使用 `readonly static` 关键字来声明 class C { static readonly StaticSymbol: unique symbol = Symbol(); }
当Symbol + const时,值的类型自动判断为unique symbol
// let SERIALIZE = Symbol("serialize-method-key"); SERIALIZE 类型为symbol const SERIALIZE = Symbol("serialize-method-key"); // SERIALIZE 类型为unique symbol
接口中的计算属性名称引用必须引用类型为文本类型或 "unique symbol"
interface Serializable { // [("serialize-method-key")](obj: {}): string; //error [SERIALIZE](obj: {}): string; } class JSONSerializableItem implements Serializable { // error 只能用引入的SERIALIZE来作属性的名称 // ["serialize-method-key"](obj: {}) { // return JSON.stringify(obj); // } [SERIALIZE](obj: {}) { return JSON.stringify(obj); } }
另外,两个unique symbol
类型的值不能互相比较(当然除非其中一个值的类型为用 typeof
另外一个值)
新编译选项,更严格的类属性检查( --strictPropertyInitialization)
TypeScript 2.7引入了一个新的控制严格性的标记 --strictPropertyInitialization
现在,如果开启 strictPropertyInitialization
,我们必须要确保每个实例的属性都会初始值,可以在构造函数里或者属性定义时赋值。
class StrictClass { foo: number; bar = 'hello'; baz: boolean; // error,Property 'baz' has no initializer and is not definitely assigned in the constructor constructor() { this.foo = 42; } }
有两种情况下我们不可避免该error的产生:
- 该属性本来就可以是
undefined
。这种情况下添加类型undefined - 属性被间接初始化了(例如构造函数中调用一个方法,更改了属性的值)。这种情况下我们可以使用 显式赋值断言 (修饰符号 !) 来帮助类型系统识别类型。后面具体介绍它,先看下代码中怎么使用:
class StrictClass { // ... baz!: boolean; // ^ // 注意到这个!标志 // 代表着显式赋值断言修饰符 }
显式赋值断言(Definite Assignment Assertions)
尽管我们尝试将类型系统做的更富表现力,但我们知道有时用户比TypeScript更加了解类型
跟上面提到的类属性例子差不多,我们无法在给一个值赋值前使用它,但如果我们已经确定它已经被赋值了,这个时候类型系统就需要我们人的介入
let x: number; initialize(); console.log(x + x); // ~ ~ // Error! Variable 'x' is used before being assigned. function initialize() { x = 10; }
添加 ! 修饰:let x!: number
,则可以修复这个问题
我们也可以在表达式中使用!,类似 variable as string
和 <string>variable
:
let x: number; initialize(); console.log(x! + x!); //ok function initialize() { x = 10; }
添加 --esModuleInterop
对ES模块和老式代码更好的互通
这一块中文官网花了挺长篇幅来讲这个内容,我也是实践过才明白讲的是什么,需要理解 __esModule
标记是做什么的、es6模块经过ts转换成commonjs是怎么的、 如何保持与老式代码( CommonJS/AMD/UMD)的互通性
先说一下 --esModuleInterop
的作用:
- 默认开启allowSyntheticDefaultImports(那是肯定的,我们需要它来实现默认导入的功能)
- 命名空间导入不允许被调用或者构造,需要改成默认导入
import * as express from "express"; // error 正确的实现导入方式应该是下面这种 import express from "express"; express();
注意: 我们强烈建议开启esModuleInterop
,不管在新代码或者是老代码上。但该模式下会可能对已有的代码产生破环,对已有的命名空间导入(import * as express from "express"; express();)改成默认导入(import express from "express"; express(); )
让我们更深入理解,在 --esModuleInterop
下,ts对 import *
和 import default
两种导入方式用两个helpers __importStar
and __importDefault
做分别处理。
构建前的代码:
import * as foo from "foo"; import b from "bar"; const a = 'newM' export default a
构建后的代码:
"use strict"; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; } var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; } exports.__esModule = true; var foo = __importStar(require("foo")); var bar_1 = __importDefault(require("bar")); const a = 'newM'; exports.default = a;
这样做的结果是,我们新的代码不管是通过ts,Babel或Webpack来构建,都能通过 __esModule
来判断是否为es模块,如果无__esModule
,则创建含default的对象保存模块,这样就完成我们要的默认导入功能,同时保持对老的库的支持
固定长度元祖
[number, string, string]
类型的值 不可赋值 [number, string]
类型的值了。
[number, string]类型等同于下面的 NumStrTuple声明:
interface NumStrTuple extends Array<number | string> { 0: number; 1: string; length: 2; // using the numeric literal type '2' }
NumStrTuple
代表类型为固定长度为2,[0]为number类型,[1]为string 类型的数组
如果不希望固定宽度,只需要最小长度,可以这样:
interface MinimumNumStrTuple extends Array<number | string> { 0: number; 1: string; }
更智能的对象字面量推断
// 现在能正常判断obj的类型了,而不是之前的 {} const obj = test ? { text: "hello" } : {}; // { text: string } | { text?: undefined } const s = obj.text; // string | undefined // { a: number, b: number } | // { a: string, b?: undefined } | // { a?: undefined, b?: undefined } let obj2 = [{ a: 1, b: 2 }, { a: "abc" }, {}][0]; declare function f<T>(...items: T[]): T; // { a: number, b: number } | // { a: string, b?: undefined } | // { a?: undefined, b?: undefined } let obj3 = f({ a: 1, b: 2 }, { a: "abc" }, {});
其它
in操作符细化和精确的 instanceof
in
操作符 和 instanceof
运算符 更好用了(就是那么简单)
--watch,--pretty 编译
--watch 会在重新编译后清空控制台
--pretty 更好地展示错误信息
看图:
完。参考:ts官方