Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

TypeScript 2.2

对混入类的支持

TypeScript 2.2 增加了对 ECMAScript 2015 混入类模式的支持(更多细节请参见 MDN 混入描述"真正的" JavaScript 类混入),以及将混入构造签名与常规构造签名在交集类型中组合的规则。

首先是一些术语

混入构造函数类型 指的是具有单个构造签名的类型,该签名具有一个 any[] 类型的单个剩余参数,并返回一个类对象类型。例如,给定一个类对象类型 Xnew (...args: any[]) => X 是一个实例类型为 X 的混入构造函数类型。

混入类 是一个 extends 类型参数类型的表达式的类声明或表达式。以下规则适用于混入类声明:

  • extends 表达式的类型参数类型必须被约束为混入构造函数类型。
  • 混入类的构造函数(如果有)必须具有一个类型为 any[] 的单个剩余参数,并且必须使用展开运算符将这些参数作为参数传递给 super(...args) 调用。

给定一个参数化类型 T 的表达式 Base,且约束为 X,混入类 class C extends Base {...} 的处理方式如同 Base 具有类型 X,结果类型是 typeof C & T 的交集。 换句话说,混入类表示为混入类构造函数类型与参数化基类构造函数类型的交集。

当获取包含混入构造函数类型的交集类型的构造签名时,混入构造签名被丢弃,它们的实例类型被混入到交集类型中其他构造签名的返回类型中。 例如,交集类型 { new(...args: any[]) => A } & { new(s: string) => B } 有一个单一的构造签名 new(s: string) => A & B

在一个示例中整合上述所有规则
ts
class 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 接口(即具有类型为 numberxy 属性)的类添加一个 getLocation 方法。

ts
interface 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 没有一种表示非原始类型的类型,即任何不是 numberstringbooleansymbolnullundefined 的类型。于是引入了新的 object 类型。

有了 object 类型,像 Object.create 这样的 API 可以更好地表示。例如:

ts
declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

支持 new.target

new.target 元属性是 ES2015 中引入的新语法。 当通过 new 创建构造函数的实例时,new.target 的值被设置为最初用于分配实例的构造函数的引用。 如果一个函数被调用而不是通过 new 构造,则 new.target 设置为 undefined

当需要在类构造函数中设置 Object.setPrototypeOf__proto__ 时,new.target 非常有用。其中一个用例是在 NodeJS v4 及更高版本中继承 Error

示例
ts
class CustomError extends Error {
  constructor(message?: string) {
    super(message); // 这里的 'Error' 破坏了原型链
    Object.setPrototypeOf(this, new.target.prototype); // 恢复原型链
  }
}

这生成了以下 JS:

js
var CustomError = (function(_super) {
  __extends(CustomError, _super);
  function CustomError() {
    var _newTarget = this.constructor;
    var _this = _super.apply(this, arguments); // 这里的 'Error' 破坏了原型链
    _this.__proto__ = _newTarget.prototype; // 恢复原型链
    return _this;
  }
  return CustomError;
})(Error);

new.target 在编写可构造函数时也很有用,例如:

ts
function f() {
  if (new.target) {
    /* 通过 'new' 调用 */
  }
}

它被转换为:

js
function f() {
  var _newTarget = this && this instanceof f ? this.constructor : void 0;
  if (_newTarget) {
    /* 通过 'new' 调用 */
  }
}

对表达式操作数中 null/undefined 的更好检查

TypeScript 2.2 改进了对表达式中可空操作数的检查。具体来说,以下情况现在被标记为错误:

  • 如果 + 运算符的任一操作数可为空,且两个操作数都不是 anystring 类型。
  • 如果 -***/%<<>>>>>&|^ 运算符的任一操作数可为空。
  • 如果 <><=>=in 运算符的任一操作数可为空。
  • 如果 instanceof 运算符的右操作数可为空。
  • 如果 +-~++-- 一元运算符的操作数可为空。

如果操作数的类型是 nullundefined,或者是包含 nullundefined 的联合类型,则认为该操作数可为空。 注意,联合类型的情况仅在 strictNullChecks 模式下发生,因为在经典类型检查模式下 nullundefined 会从联合中消失。

具有字符串索引签名的类型的点属性

具有字符串索引签名的类型可以使用 [] 符号进行索引,但不允许使用点号。 从 TypeScript 2.2 开始,两者都应该被允许。

ts
interface StringMap<T> {
  [x: string]: T;
}

const map: StringMap<number>;

map["prop1"] = 1;
map.prop2 = 2;

这仅适用于具有显式字符串索引签名的类型。在类型上使用点号访问未知属性仍然是错误。

支持 JSX 元素子元素上的展开运算符

TypeScript 2.2 增加了对在 JSX 元素子元素上使用展开的支持。 更多细节请参见 facebook/jsx#57

示例
ts
function 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 语法。 新的 jsxreact-native 将保留输出文件中的 JSX 语法,但为其赋予 .js 扩展名。