玩转 TypeScript:进阶篇

CDEC - Jay

Agenda

  • 函数 & 泛型
  • 模块 & 命名空间
  • 配置 tsconfig.json
  • TypeScript 模块解析
  • 编写 d.ts 类型定义文件

函数 与 泛型

函数

  • 参数类型,返回值类型
  • 可选参数
  • 剩余参数
  • this参数
  • 函数重载

参数类型,返回值类型


  function add(x: number, y: number): number {
    return x + y;
  }
            

可选参数


  function add(x: number, y: number, z?: number){
    return x + y + (z || 0);
  }
  add(1, 2);
  add(1, 2, 3);
  add(1, 2, 3, 4); // Expected 2-3 arguments, but got 4.
            

剩余参数


  function add(x: number, y: number, ...nums: number[]) {
    return x + y + nums.reduce((current, item, arr) => { current += item; return current; }, 0);
  }

  add(1, 2, 3, 4, 5); // 15
            

this参数


  type FunThisType = { a: number, b: string };
  // 限定 this 的类型
  function fun(this: FunThisType) {
    return this.a;
  }
            

函数重载


  function plus(a1: number, a2: number): number;
  function plus(a1: string, a2: string): string;
  function plus(a1, a2): any {
    if (typeof a1 === 'number') {
      return a1 + a2;
    }
    return a1 + a2;
  }

  plus(1, 2); // 3
  plus(1, '2'); // Argument of type '1' is not assignable to parameter of type 'string'.
  plus('1', '2'); // '12'
            

泛型

  • 使用泛型
  • 泛型类
  • 泛型约束

使用泛型


  function fun<T>(a: T): string {
    return typeof a;
  }
  // 函数变量使用泛型
  const fun2: <T>(a: T) => string = a => {
    return typeof a;
  }

  fun<number>(1);
  fun('a');
  fun<number>('2'); // Argument of type '"2"' is not assignable to parameter of type 'number'.
            

泛型类


  class Store<T>{
    valueArr: T[];
    push(val: T) {
      this.valueArr.push();
    }
    pop() {
      return this.valueArr.pop();
    }
  }

  const numberStore = new Store<number>();
  numberStore.push(1);
  numberStore.push('1'); // Argument of type '"1"' is not assignable to parameter of type 'number'.
              

泛型约束


  class Person {
    name: string;
  }
  class Man extends Person {

  }
  function createUser<T extends Person>(user: T) {
    return user;
  }
  createUser<Person>(Man);
  createUser('abc'); // Argument of type '"abc"' is not assignable to parameter of type 'Person'.
                

  function getProp<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
  }

  const obj = { a: 1, b: 'b' };

  getProp(obj, 'a')
  getProp(obj, 'c'); // Argument of type '"c"' is not assignable to parameter of type '"b" | "a"'.
              

模块 & 命名空间

“模块化”规范

ES6 Module Usage


  // a.ts
  export const a = 1;
  export default 2;

  // b.ts
  import { a } from './a';
  import defaultImport from './a';
  // 重新导出
  export const newExport = defaultImport;
  // 别名导出
  export { a as value } from './a';
            

命名空间


  // developer A
  var a = 1;
  function getA() {
    return a;
  }
  // developer B(after 500 lines)
  var a = 2;
  // developer A
  getA();
              

  // developer A
  var DA = {
    a: 1,
    getA(){
      return this.a;
    }
  }
  // developer B
  var DB = {
    a: 2
  }
  // developer A
  DA.getA();
              

配置 tsconfig.json

tsconfig.json 基本结构


  {
    "compilerOptions": {
      "module": "commonjs", // 生成的模块类型
      "noImplicitAny": true, // 是否允许TypeScript 推断 any。
      "strictNullChecks": true, // 强制 null 检查(null 值只能用于null类型)
      "removeComments": true, // 移除注释
      "sourceMap": true // 开启 SourceMap
    },
    "files": [
      "hello.ts"
    ],
    "include": [],
    "exclude": []
  }
            

注意:在命令行上指定的编译选项会覆盖在 tsconfig.json 文件里的相应选项。

常用编译属性

  • noImplicitAny
  • strictNullChecks
  • noImplicitThis
  • noImplicitReturns
  • ...

noImplicitAny

是否禁用无法推断类型时,自动使用 any。如果为true,则必须显示设置无法推断的类型。建议设置为 true

  // If true
  function f1(a) { // Parameter 'a' implicitly has an 'any' type.
  }
  function f2(a: any) {
  }
            

strictNullChecks

是否禁用 null 作为任意类型的子类型。如果是,则禁用,意味着 null类型的值只能是null

  // If true
  var a: void = null; // Type 'null' is not assignable to type 'void'.
  var b: undefined = null; // Type 'null' is not assignable to type 'undefined'.
  var c: null = null;
              

noImplicitThis

当 this 表达式的值为 any 类型的时候,生成一个错误。

  // If true
  // 'this' implicitly has type 'any' because it does not have a type annotation.
  function Greeter(greeting: string) {
    this.greeting = greeting;
  }
                

noImplicitReturns

是否强制函数所有分支都要有 return 。设置为true,则强制

  // If true
  function fun(a: any) { // Not all code paths return a value.
    if (a == 1) {
      return 'abb';
    }
  }
                  

files & include & exclude

  • 如果 files 以及 include 没有指定,则默认编译当前目录以及子目录下的所有 TypeScript 文件,同时排除 exclude 中指定的文件(如果有)。
  • include 引入的文件,总是可以通过 exclude 排除。files 指定的文件,无法使用 exclude 排除。
  • 任何被 files 或 include 指定的文件所引用的文件也会被包含进来。
  • exclude 默认情况下会排除 node_modules,bower_components,jspm_packages 和<outDir> 目录。
  • 不推荐只有扩展名的不同来区分同目录下的文件。假设我们包含了index.ts,那么index.d.ts和index.js会被排除在外。

@types & typeRoots & types

  • 默认所有层级的 @types 都会被包含(直到根目录)。
  • 如果设置了 typeRoots,则只有 typeRoots 下的包才会被包含。
  • 如果设置了 types,则只有设置的包才会被包含
  • 指定 "types": [] 可以禁用自动引入 @types 包。

TypeScript 模块解析

模块解析策略

相对 VS. 非相对 导入

路径映射


  {
    "compilerOptions": {
      "baseUrl": ".",
      "paths": {
        "jquery": ["node_modules/jquery/dist/jquery"] // 此处映射是相对于"baseUrl"
      }
    }
  }
            

编写 d.ts 类型定义文件

一个 d.ts 文件 Demo


  /// <reference types="node" />
  import * as http from "http";

  declare function createServer(): createServer.Server;

  declare namespace createServer {
      export type ServerHandle = HandleFunction | http.Server;

      type NextFunction = (err?: any) => void;

      export type SimpleHandleFunction = (req: http.IncomingMessage, res: http.ServerResponse) => void;
      export type NextHandleFunction = (req: http.IncomingMessage, res: http.ServerResponse, next: NextFunction) => void;
      export type ErrorHandleFunction = (err: any, req: http.IncomingMessage, res: http.ServerResponse, next: NextFunction) => void;
      export type HandleFunction = SimpleHandleFunction | NextHandleFunction | ErrorHandleFunction;

      export interface ServerStackItem {
          route: string;
          handle: ServerHandle;
      }

      export interface Server extends NodeJS.EventEmitter {
          (req: http.IncomingMessage, res: http.ServerResponse, next?: Function): void;

          route: string;
          stack: ServerStackItem[];
          use(fn: HandleFunction): Server;
          use(route: string, fn: HandleFunction): Server;
          handle(req: http.IncomingMessage, res: http.ServerResponse, next: Function): void;
          listen(port: number, hostname?: string, backlog?: number, callback?: Function): http.Server;
          listen(port: number, hostname?: string, callback?: Function): http.Server;
          listen(path: string, callback?: Function): http.Server;
          listen(handle: any, listeningListener?: Function): http.Server;
      }
  }

  export = createServer;
            

快速引入一个外部模块


  import a from 'ext-lib';
            

  // global.d.ts
  declare module 'ext-lib';
              

扩展已有的类型定义


  // 以 koa 为例
  import * as koa from 'koa';
  const app = new koa();

  app.use((ctx: koa.Context) => {
    ctx.request.abc;
    ctx.request.ddd; // Property 'ddd' does not exist on type 'Request'.
  });

  declare module 'koa' {
    export interface BaseRequest {
      abc: string;
    }
  }
          

如何发布 d.ts 文件?

  1. 与你的 npm 包一起发布,通过 package.json -> types (typings同义)指定 d.ts 文件
  2. 发布到 npm 上的 @types organization(提交 PR 到 https://github.com/DefinitelyTyped/DefinitelyTyped)

Q? A!

Thanks!