TypeScript进阶篇(二)

1,024 阅读12分钟

一、TS中的配置文件

基础配置

执行命令

tsc --init 

生成tsconfig.json文件,也就是TS的编译配置文件。

{
  "compilerOptions": {
    /* Basic Options */
    // "incremental": true,                   /* Enable incremental compilation */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}

编译TS命令,直接指定编译某个ts文件的时候并不会使用tsconfig.json中的内容,只有直接运行tsc时,才会走tsconfig.json中的配置

tsc dome.ts

只有直接运行tsc时,才会走tsconfig.json中的配置。在未配置tsconfig.json的情况下,会执行编译所有的ts文件

tsc // 在未配置tsconfig.json的情况下,会执行编译所有的ts文件

tsc命令会找tsconfig.json,

"include": ["./demo.ts"], // 指定只编译demo.ts文件
"exclude": ["./demo.ts"], // 排除编译demo.ts文件,就不会编译demo.ts
"files": ["./demo.ts"], // files与include功能相同
"removeComments": true // 编译中移除注释

也可以写成

"include": ["src/**/*"], // ** 代表任何目录,* 代表任何文件

参考文档: tsconfig-json

compilerOptions字面意思:就是编译过程中的一些属性或配置。

"compilerOptions": {
  "removeComments": true,  // 编译中移除注释
  "noImplicitAny": false, // 不要求显示的设置any,false就是可以,这样变量不定义类型也不会报错。默认true
  "strictNullChecks": false, // 强制检查null类型。默认true。否则编译会报错。
}

const children: string = null // 不允许把null复制给其他基础类型 strictNullChecks设置为false则不会报错,编译也不会报错。

进阶配置

  1. 指定文件的输入地址,和文件的生成地址。
"rootDir": "./src",  // 输入地址
"outDir": "./build",  // 生成地址
  1. 增量编译。只编译新增内容,之前编译过的不会再编译。打开后,首次编译之后会生成tsconfig.tsbuildinfo文件,用来记录上次编译过程中的信息,再次编译的时候就会和上一次做比对。是一个增量式的配置项。
"incremental": true,  // 增量编译
  1. 是否允许对js项也进行编译。
"target": "es5", // 将代码编译成es5
"allowJs": true,  // 是否允许对js项也进行编译

编译前:

export const name = 'dell'

编译后

"use strict"
Object.defineProperty(exports, "__esModule", {value: true})
exports.name = 'dell'
  1. 对语法进行检测。
"checkJs": true, // 对js文件进行语法检测
  1. 生成sourceMap文件,***.js.map
"sourceMap": true,
  1. 变量未被使用时警告。
"noUnusedLocals": true, 
  1. 函数的参数未被使用时警告。
 "noUnusedParameters": true, 

参考文档:编译选项

二、TS中的联合类型Unin type和类型保护

用类型保护解决联合类型遇到的问题

  1. 类型断言 as
interface Bird {
  fly: boolean,
  sing: () => {},
}

interface Dog {
  fly: boolean,
  bark: () => {},
}

// 联合类型 animal: Bird | Dog
function trainAnimal (animal: Bird | Dog) {
  // 类型保护 sing和bark是独有的属性
  // 类型断言 (animal as Bird) 传入的animal是Bird的时候
  if (animal.fly) { // Bird才会飞true
    (animal as Bird).sing() // as 断言
  } else {
    (animal as Dog).bark()
  }
}
  1. 类型保护,用 in语法
function trainAnimalSecond (animal: Bird | Dog) {
  if ('sing' in animal) {
    animal.sing()
  } else {
    animal.bark()
  }
}
  1. 类型保护,用 typeof语法

function add (first: string | number, second: string | number) {
  if (typeof first === 'string' || typeof second === 'string') {
    return `${first}${second}`
  }
  return first + second
}
  1. 类型保护,用 instanceof语法,只有为class类的时候才可以使用,interface不能用instanceof
class NumberObj {
  public count: number
}

function addSecond (first: object | NumberObj, second: object | NumberObj) {
  if (first instanceof NumberObj && second instanceof NumberObj) {
    return first.count + second.count
  }
  return 0
}

二、TS中的枚举类型Enum

  1. 用js来模拟枚举
const Status = {
  OFFLINE: 0,
  ONLINE: 1,
  DELETED: 2
};

function getResult(status) {
  if (status === Status.OFFLINE) {
    return 'offline';
  } else if (status === Status.ONLINE) {
    return 'online';
  } else if (status === Status.DELETED) {
    return 'deleted';
  } else {
    return 'error';
  }
}

const result = getResult(Status.OFFLINE);
console.log(result); // online
  1. ts来写枚举
enum Status {
  OFFLINE,
  ONLINE,
  DELETED
}
// 枚举默认值为 0 1 2
console.log(Status.OFFLINE); // 0
console.log(Status.ONLINE);  // 1
console.log(Status.DELETED); // 2

function getResult(status) {
  if (status === Status.OFFLINE) {
    return 'offline';
  } else if (status === Status.ONLINE) {
    return 'online';
  } else if (status === Status.DELETED) {
    return 'deleted';
  } else {
    return 'error';
  }
}

const result = getResult(Status.OFFLINE);
const result1 = getResult(0);
console.log(result); // offline
console.log(result1); // offline

输出结果,依旧不变。

改变offline的枚举值

enum Status {
  OFFLINE = 1,
  ONLINE,
  DELETED
}
console.log(Status.OFFLINE); // 1
console.log(Status.ONLINE);  // 2
console.log(Status.DELETED); // 3
enum Status {
  OFFLINE, // 默认从0 开始
  ONLINE = 4,
  DELETED //  从4开始加1
}
console.log(Status.OFFLINE); // 0
console.log(Status.ONLINE);  // 4
console.log(Status.DELETED); // 5

枚举类型还可以反查结果(反向映射),如下:

enum Status {
  OFFLINE,
  ONLINE,
  DELETED
}
console.log(Status[0]); // OFFLINE

enum是一个更灵活的数据结构。

使用场景

  1. 和我们的例子类似,当我们的某一个状态的值是固定的几个值,此时就可以用枚举类型来表示这几个值,更加清晰易懂。
  2. 枚举默认值从0开始,也可以手动修改枚举值。
  3. 还可以通过对应下标0、1、2...反向查枚举的值。如果枚举值被修改,则需要写对应修改的值即可,如console.log(Status[4])ONLINE

小插曲:在安装typescirpt的时候竟然报了如下错误,原因是项目的name也是typescript,修改之后即可正常安装。

npm ERR! code ENOSELF
npm ERR! Refusing to install package with name "typescript" under a package
npm ERR! also called "typescript". Did you name your project the same
npm ERR! as the dependency you're installing?
npm ERR! 
npm ERR! For more information, see:
npm ERR!     <https://docs.npmjs.com/cli/install#limitations-of-npms-install-algorithm>
{
  "name": "typescript",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "devDependencies": {
    "ts-node": "^8.6.2"
  },
  "scripts": {
    "dev": "ts-node ./src/enum.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

四、TS中的函数泛型

泛型generic:泛指的类型,写法<>

针对不确定的类型就可以用泛型语法,在使用的时候再指定具体的对应值

示例1

// <ABC> 定义名字为ABC的泛型,两个传入的参数都要求为同一类型
function join<ABC>(first: ABC, second: ABC) {
  return `${first}${second}`
}
// 在使用时给ABC指定类型,定义类型都为string,传入的值就必须为string类型
join<string>('1','2')

如下写法,则无法做到两个参数传入值必须为同一类型的要求。

function join(first: string | number, second: string | number) {
  return `${first}${second}`
}
join('1',2)

示例2

// function map<ABC> (params: Array<ABC>) {
function map<ABC> (params: ABC[]) {
  return params
}
map<string>(['1', '2', '3'])

常见用T来代替ABC,也就是Type的简写

// function map<T> (params: Array<T>) {
function map<T> (params: T[]) {
  return params
}
map<string>(['1', '2', '3'])

我们还可以定义两个泛型或多个泛型

function join<T, P>(first: T, second: P) {
  return `${first}${second}`;
}
join<string, number>('1', 2); // 显式声明类型
join<('1', 2) // 不写类型则会默认推断

不写类型则会默认推断(类型推断),如下图:

声明返回类型也用T这个泛型,如下:

function anotherJoin<T>(first: T, second: T): T {
  return first;
}
anotherJoin<string>('1', '2');

泛型和普通的类型使用基本是一样的。使用的时候需要在函数前用<>做一个声明

五、TS中类的泛型以及泛型类型

Demo1

class DataManager<T> {
  constructor(private data: T[]) {}
  getItem(index: number): T {
    return this.data[index];
  }
}
const data = new DataManager<number>([2]);
data.getItem(0);

Demo2: 让泛型继承某一属性

interface Item {
  name: string;
}
class DataManager<T extends Item> {
  constructor(private data: T[]) {}
  getItem(index: number): string {
    return this.data[index].name;
  }
}
const data = new DataManager([{ name: '2' }]);
data.getItem(0);

Demo3: 泛型是numberstring<T extends number | string>

class DataManager<T extends number | string> {
  constructor(private data: T[]) {}
  getItem(index: number): T {
    return this.data[index];
  }
}
const data = new DataManager<number>([2])

Demo4: 如何使用泛型作为一个具体的类型注解

// 泛型还可以当做type的声明
function hello<T>(params: T) {
  return params
}
const func:<T>(param: T) => T = hello

六、TS中命名空间-namespace

过多的全局变量一定会让页面变的不可维护。

编译前:

// 把所有东西都放到Home这样的命名空间中,防止Header、Content、Footer都暴露在全局环境中
namespace Home {
  class Header {
    constructor() {
      const element = document.createElement('div');
      element.innerHTML = 'This is Header';
      document.body.appendChild(element);
    }
  }

  class Content {
    constructor() {
      const element = document.createElement('div');
      element.innerHTML = 'This is Content';
      document.body.appendChild(element);
    }
  }

  class Footer {
    constructor() {
      const element = document.createElement('div');
      element.innerHTML = 'This is Footer';
      document.body.appendChild(element);
    }
  }
  export class Page { // 导出Page
    constructor() {
      new Header();
      new Content();
      new Footer();
    }
  }
}

new Home.Page();

编译后:

// 把所有东西都放到Home这样的命名空间中
var Home;
(function (Home) {
    var Header = /** @class */ (function () {
        function Header() {
            var element = document.createElement('div');
            element.innerHTML = 'This is Header';
            document.body.appendChild(element);
        }
        return Header;
    }());
    var Content = /** @class */ (function () {
        function Content() {
            var element = document.createElement('div');
            element.innerHTML = 'This is Content';
            document.body.appendChild(element);
        }
        return Content;
    }());
    var Footer = /** @class */ (function () {
        function Footer() {
            var element = document.createElement('div');
            element.innerHTML = 'This is Footer';
            document.body.appendChild(element);
        }
        return Footer;
    }());
    var Page = /** @class */ (function () {
        function Page() {
            new Header();
            new Content();
            new Footer();
        }
        return Page;
    }());
    Home.Page = Page; // 将Page方法暴露出来
})(Home || (Home = {}));
new Home.Page();

这种写法可以让我们尽少的声明全局变量,把一组相关的内容封装到一起,对外提供统一的接口。

// tsconfig.json 
"outFile": "./build/page.js", // 将多个ts文件统一输出到一个js文件中./build/page.js文件中
"module": "amd"

将文件拆成page.tscomponents.ts

// components.ts
namespace Components {
  // 子命名空间
  export namespace SubComponents {
    export class Test{}
  }
  // 导出接口
  export interface User {
    name: string;
  }
  
  export class Header {
    constructor() {
      const element = document.createElement('div');
      element.innerHTML = 'This is Header';
      document.body.appendChild(element);
    }
  }

  export class Content {
    constructor() {
      const element = document.createElement('div');
      element.innerHTML = 'This is Content';
      document.body.appendChild(element);
    }
  }

  export class Footer {
    constructor() {
      const element = document.createElement('div');
      element.innerHTML = 'This is Footer';
      document.body.appendChild(element);
    }
  }
}

// page.ts
依赖的声明,namespace之间相互引用的声明,用///表示
/// <reference path='./components.ts' />
namespace Home {
  export class Page {
    user: Components.User = {
        name: 'fruit'
    }
    constructor() {
      new Components.Header();
      new Components.Content();
      new Components.Footer();
    }
  }
}

new Home.Page();

七、TS中import对应的模块化

amd语法浏览器是不支持的

// components.ts
export class Header {
  constructor() {
    const element = document.createElement('div');
    element.innerHTML = 'This is Header';
    document.body.appendChild(element);
  }
}

export class Content {
  constructor() {
    const element = document.createElement('div');
    element.innerHTML = 'This is Content';
    document.body.appendChild(element);
  }
}

export class Footer {
  constructor() {
    const element = document.createElement('div');
    element.innerHTML = 'This is Footer';
    document.body.appendChild(element);
  }
}

import 模块引入(es6用法)

// page.ts
import { Header, Content, Footer } from './components';
class Page {
  constructor() {
    new Header();
    new Content();
    new Footer();
  }
}

new Page();

参考文档:

www.zhihu.com/question/20…

zhuanlan.zhihu.com/p/41231046

zhuanlan.zhihu.com/p/25107397?…

八、使用Parcel打包TS代码

Parcel:是和webpack类似的打包工具,不需要做过多配置,使用起来相对简单。

npm init -y
tsc --init

parcel : github.com/parcel-bund…

npm install parcel@next -D 
// package.json
"scripts": {
    "test": "parcel ./src/index.html"
 },

运行

npm run test

在浏览器访问http://localhost:1234/即可查看相关运行结果

九、描述文件中的全局类型

类型定义文件***.d.ts 如何在.d.ts文件里对全局函数和全局变量进行定义

// jquery.d.ts
// 定义全局变量
declare var $: (param: () => void) => void;
// 定义全局函数
declare function $(param: () => void): void;
// 允许一个函数多次定义。函数的重载,根据传入参数的不同变化
declare function $(params: string): {
  html: (html: string) => {}
}

写法优化

// 定义全局函数
interface JqueryInstance {
  html: (html: string) => JqueryInstance;
}
// 函数重载
declare function $(readyFunc: () => void): void;
declare function $(selector: string): JqueryInstance;

使用$就不会报错

$(function() {
  $('div').html('<div>123</div>');
});

为什么我们要安装或自己写类型定义文件,帮助我们的ts文件,理解js文件或js库中的内容。比如$

**.d.ts为类型描述文件或类型定义文件

使用interface实现函数重载

// $只是不同的函数,只是对函数重载的时候,可以用下面的语法
interface JQuery {
  (readyFunc: () => void): void
  (selector: string): JqueryInstance
}
declare var $: JQuery;
// 如何对对象进行类型定义,以及如何对类进行类型定义,以及命名空间的嵌套
// 既让$是函数,又让$是对象,就可以用下面的语法
declare namespace $ { // 在全局有对象,可以用namespace来构建这个对象
  namespace fn {
    class init {}
  }
}

// 引入外部的库,TS无法识别,我们就可以用全局声明内容的语法,让ts能够理解,$里都有哪些内容
$(function() {
  $('div').html('<div>123</div>');
  new $.fn.init();
});

引入外部的库,TS无法识别,我们就可以用全局声明内容的语法,让TS能够理解,$里都有哪些内容

参考文档:TypeScript error: Property 'X' does not exist on type 'Window'

当我们使用window时,比如 let a = window.a,将a挂载到window下,此时TS会报如下错误:

TypeScript error: Property 'a' does not exist on type 'Window'. TS2339

如下,使用type定义依旧无效

type Window = {
  a: any
}

let a = window.a;

解决方法

1. 推荐
declare global {
    interface Window {
        a:any;
    }
}

let a = window.a;
2. 不推荐
declare const window: any;
let a = window.a;

十、模块代码的类型描述文件

es6、commonjs、UMD 等在TS中如何定义

npm install jquery --save
import $ from 'jquery'

报如下错误

此时我们需要模块化的描述文件。

ES6 模块化的类型注解文件,就是这么写出来的。Commonjs UMD等写法和ES6写法稍有不同,可查阅相关资料。

// jquery.d.ts
// ES6 模块化
declare module 'jquery' {
  interface JqueryInstance {
    html: (html: string) => JqueryInstance;
  }
  // 混合类型
  function $(readyFunc: () => void): void;
  function $(selector: string): JqueryInstance;
  namespace $ {
    namespace fn {
      function init(): void;
    }
  }
  // 导出
  export = $;
}

这样使用$就不会报错了

import $ from 'jquery';

$(function() {
  $('div').html('<div>123</div>');
  new $.fn.init();
});

十一、泛型中keyof语法的使用

interface Persone {
  name: string;
  age: number;
  gender: string;
}
class Student {
  constructor(private info: Persone) {}
  getInfo(key: string) {
    return this.info[key]; // 我们无法确定key的值
  }
}

const student1 = new Student({
  name: 'fruit',
  age: 18,
  gender: 'male'
});

const studentName = student1.getInfo('name');

studentName的类型返回的为any

以上的写法,key值是不安全的,当传入的值为nameagegender之外的值时,就会返回undefined,因此我们需要类型保护来解决这个问题。

修改写法:但不够好,传入这三个值之外的值是,还是会返回undefined

getInfo(key: string) {
  if (key === 'name' || key === 'age' || key === 'gender') {
    return this.info[key];
  }
}

泛型结合keyof来写

type T = 'name'
key: 'name'
Person1['name']

type T = 'age'
key: 'age'
Person1['age']

type T = 'gender'
key: 'gender'
Person1['gender']
interface Person1 {
  name: string;
  age: number;
  gender: string;
}

class Student {
  constructor(private info: Person1) {}
  getInfo<T extends keyof Person1>(key: T): Person1[T] {
    return this.info[key];
  }
}

const student1 = new Student({
  name: 'fruit',
  age: 18,
  gender: 'male'
});

const studentName = student1.getInfo('hello'); // 不能传hello,只能是name、age、gender

当我们定义一个类型的时候,可以让类型不是string、number等基础类型,也可以是interface、{}对象等复杂类型,我们的类型甚至是固定的字符串,如:type T = 'name',类型的值就是一个字符串。

type A = 'NAME'
const a: A = 'bc'

报如下错误:

// 这样就不报错,变量的值必须和type'NAME'字符串一样的东西
type A = 'NAME'
const a: A = 'NAME'

因此,类型也可以是一个字符串。正是因为这个原理,我们才可以用keyof语法,结合泛型,实现上述效果(只能传递对应的key)。如果有一个类,里面有一个对象,我们想根据index、或key值获取对象里的某项内容时,又想要推断出正确的返回类型,就可以用类似这样的语法来解决<T extends keyof Person1>(key: T): Person1[T]