混入(Mix-in)类支持
TypeScript 2.2 增加了对 ECMAScript 2015 混入类模式的支持(更多详情请参阅 MDN 混入描述 和 “Real” Mixins with JavaScript Classes),以及在交叉类型中结合混入构造签名与常规构造签名的规则。
首先是一些术语
混入构造函数类型(mixin constructor type)是指具有单个构造签名、且该签名包含一个 any[] 类型的剩余参数(rest argument)并返回对象类型的一种类型。例如,对于一个对象类型 X,new (...args: any[]) => X 就是一个实例类型为 X 的混入构造函数类型。
混入类(mixin class)是 extends 一个类型参数表达式的类声明或表达式。混入类声明适用以下规则:
extends表达式中的类型参数类型必须约束为混入构造函数类型。- 混入类的构造函数(如果有)必须包含一个
any[]类型的剩余参数,并且必须使用展开运算符(spread operator)在super(...args)调用中传递这些参数。
给定一个具有约束 X 的参数化类型 T 的表达式 Base,混入类 class C extends Base {...} 的处理方式就像 Base 具有类型 X 一样,其生成的类型是交叉类型 typeof C & T。换句话说,混入类被表示为混入类构造函数类型与参数化基类构造函数类型之间的交集。
当获取包含混入构造函数类型的交叉类型的构造签名时,混入构造签名会被丢弃,其实例类型会混合到交叉类型中其他构造签名的返回类型中。例如,交叉类型 { new(...args: any[]) => A } & { new(s: string) => B } 只有一个构造签名 new(s: string) => A & B。
将以上所有规则汇总到一个示例中
tsclass Point {constructor(public x: number, public y: number) {}}class Person {constructor(public name: string) {}}type Constructor<T> = new (...args: any[]) => T;function Tagged<T extends Constructor<{}>>(Base: T) {return class extends Base {_tag: string;constructor(...args: any[]) {super(...args);this._tag = "";}};}const TaggedPoint = Tagged(Point);let point = new TaggedPoint(10, 20);point._tag = "hello";class Customer extends Tagged(Person) {accountBalance: number;}let customer = new Customer("Joe");customer._tag = "test";customer.accountBalance = 0;
混入类可以通过在类型参数的约束中指定构造签名的返回类型,来限制它们可以混入的类类型。例如,下面的 WithLocation 函数实现了一个子类工厂,它为任何满足 Point 接口(即具有 x 和 y 属性且类型为 number)的类添加了 getLocation 方法。
tsinterface Point {x: number;y: number;}const WithLocation = <T extends Constructor<Point>>(Base: T) =>class extends Base {getLocation(): [number, number] {return [this.x, this.y];}};
object 类型
TypeScript 之前没有一种类型可以代表非原始类型,即任何不是 number、string、boolean、symbol、null 或 undefined 的类型。现在引入了新的 object 类型。
有了 object 类型,像 Object.create 这样的 API 可以得到更好的表述。例如:
tsdeclare function create(o: object | null): void;create({ prop: 0 }); // OKcreate(null); // OKcreate(42); // Errorcreate("string"); // Errorcreate(false); // Errorcreate(undefined); // Error
对 new.target 的支持
new.target 元属性是 ES2015 引入的新语法。当通过 new 创建构造函数的实例时,new.target 的值被设置为对最初用于分配实例的构造函数的引用。如果函数是直接调用而非通过 new 构造,则 new.target 被设置为 undefined。
当需要在类构造函数中设置 Object.setPrototypeOf 或 __proto__ 时,new.target 非常有用。一个典型的用例是在 NodeJS v4 及更高版本中继承 Error。
示例
tsclass CustomError extends Error {constructor(message?: string) {super(message); // 'Error' breaks prototype chain hereObject.setPrototypeOf(this, new.target.prototype); // restore prototype chain}}
这将生成如下 JS:
jsvar CustomError = (function(_super) {__extends(CustomError, _super);function CustomError() {var _newTarget = this.constructor;var _this = _super.apply(this, arguments); // 'Error' breaks prototype chain here_this.__proto__ = _newTarget.prototype; // restore prototype chainreturn _this;}return CustomError;})(Error);
new.target 在编写可构造函数时也很有用,例如:
tsfunction f() {if (new.target) {/* called via 'new' */}}
它会被翻译为:
jsfunction f() {var _newTarget = this && this instanceof f ? this.constructor : void 0;if (_newTarget) {/* called via 'new' */}}
更好地检查表达式操作数中的 null/undefined
TypeScript 2.2 改进了表达式中可空操作数的检查。具体来说,以下情况现在会被标记为错误:
- 如果
+运算符的任一操作数是可空的,且没有操作数是any或string类型。 - 如果
-,*,**,/,%,<<,>>,>>>,&,|, 或^运算符的任一操作数是可空的。 - 如果
<,>,<=,>=, 或in运算符的任一操作数是可空的。 - 如果
instanceof运算符的右操作数是可空的。 - 如果
+,-,~,++, 或--一元运算符的操作数是可空的。
如果操作数的类型是 null 或 undefined,或者是包含 null 或 undefined 的联合类型,则认为该操作数是可空的。请注意,联合类型的情况仅发生在 strictNullChecks 模式下,因为在经典类型检查模式下,null 和 undefined 会从联合类型中消失。
针对具有字符串索引签名的类型的点属性访问
具有字符串索引签名的类型可以使用 [] 符号进行索引,但以前不允许使用 . 符号。从 TypeScript 2.2 开始,两者都将被允许。
tsinterface StringMap<T> {[x: string]: T;}const map: StringMap<number>;map["prop1"] = 1;map.prop2 = 2;
这仅适用于具有显式字符串索引签名的类型。使用 . 符号访问类型上的未知属性仍然是一个错误。
支持在 JSX 元素子级上使用展开运算符
TypeScript 2.2 增加了在 JSX 元素子级上使用展开运算符的支持。详情请参阅 facebook/jsx#57。
示例
tsfunction Todo(prop: { key: number; todo: string }) {return <div>{prop.key.toString() + prop.todo}</div>;}function TodoList({ todos }: TodoListProps) {return (<div>{...todos.map(todo => <Todo key={todo.id} todo={todo.todo} />)}</div>);}let x: TodoListProps;<TodoList {...x} />;
新增 jsx: react-native
React-native 构建流程要求所有文件都具有 .js 扩展名,即使文件包含 JSX 语法。新的 jsx 值 react-native 将在输出文件中保留 JSX 语法,但会赋予其 .js 扩展名。