你可能不知道的 TypeScript 高級技巧

前言

在 2020 年的今天,TS 已經愈來愈火,不論是服務端(Node.js),仍是前端框架(Angular、Vue3),都有愈來愈多的項目使用 TS 開發,做爲前端程序員,TS 已經成爲一項必不可少的技能,本文旨在介紹 TS 中的一些高級技巧,提升你們對這門語言更深層次的認知。javascript

Typescript 簡介

  • ECMAScript 的超集 (stage 3)
  • 編譯期的類型檢查
  • 不引入額外開銷(零依賴,不擴展 js 語法,不侵入運行時)
  • 編譯出通用的、易讀的 js 代碼

Typescript = Type + ECMAScript + Babel-Litecss

Typescript 設計目標: github.com/Microsoft/T…html

爲何使用 Typescript

  • 增長了代碼的可讀性和可維護性
  • 減小運行時錯誤,寫出的代碼更加安全,減小 BUG
  • 享受到代碼提示帶來的好處
  • 重構神器

基礎類型

  • boolean
  • number
  • string
  • array
  • tuple
  • enum
  • void
  • null & undefined
  • any & unknown
  • never

anyunknown 的區別

  • any: 任意類型
  • unknown: 未知的類型

任何類型都能分配給 unknown,但 unknown 不能分配給其餘基本類型,而 any 啥都能分配和被分配。前端

let foo: unknown

foo = true // ok
foo = 123 //ok

foo.toFixed(2) // error

let foo1: string = foo // error
複製代碼
let bar: any

bar = true // ok
bar = 123 //ok

foo.toFixed(2) // ok

let bar1:string  = bar // ok
複製代碼

能夠看到,用了 any 就至關於徹底丟失了類型檢查,因此你們儘可能少用 any,對於未知類型能夠用 unknownvue

unknown 的正確用法

咱們能夠經過不一樣的方式將 unknown 類型縮小爲更具體的類型範圍:java

function getLen(value: unknown): number {
  if (typeof value === 'string') {
    // 由於類型保護的緣由,此處 value 被判斷爲 string 類型
  	return value.length
  }
  
  return 0
}
複製代碼

這個過程叫類型收窄(type narrowing)。node

never

never 通常表示哪些用戶沒法達到的類型。在最新的 typescript 3.7 中,下面代碼會報錯:react

// never 用戶控制流分析
function neverReach (): never {
  throw new Error('an error')
}

const x = 2

neverReach()

x.toFixed(2)  // x is unreachable
複製代碼

never 還能夠用於聯合類型的 幺元webpack

type T0 = string | number | never // T0 is string | number
複製代碼

函數類型

幾種函數類型的返回值類型寫法

function fn(): number {
  return 1
}

const fn = function (): number {
  return 1
}

const fn = (): number => {
  return 1
}

const obj = {
  fn (): number {
    return 1
  }
}
複製代碼

() 後面添加返回值類型便可。c++

函數類型

ts 中也有函數類型,用來描述一個函數:

type FnType = (x: number, y: number) => number
複製代碼

完整的函數寫法

let myAdd: (x: number, y: number) => number = function(x: number, y: number): number {
  return x + y
}

// 使用 FnType 類型
let myAdd: FnType = function(x: number, y: number): number {
  return x + y
}

// ts 自動推導參數類型
let myAdd: FnType = function(x, y) {
  return x + y
}
複製代碼

函數重載?

js由於是動態類型,自己不須要支持重載,ts爲了保證類型安全,支持了函數簽名的類型重載。即:

多個重載簽名和一個實現簽名

// 重載簽名(函數類型定義)
function toString(x: string): string;
function toString(x: number): string;

// 實現簽名(函數體具體實現)
function toString(x: string | number) {
  return String(x)
}

let a = toString('hello') // ok
let b = toString(2) // ok
let c = toString(true) // error
複製代碼

若是定義了重載簽名,則實現簽名對外不可見

function toString(x: string): string;

function toString(x: number): string {
  return String(x)
}

len(2) // error
複製代碼

實現簽名必須兼容重載簽名

function toString(x: string): string;
function toString(x: number): string; // error

// 函數實現
function toString(x: string) {
  return String(x)
}
複製代碼

重載簽名的類型不會合並

// 重載簽名(函數類型定義)
function toString(x: string): string;
function toString(x: number): string;

// 實現簽名(函數體具體實現)
function toString(x: string | number) {
  return String(x)
}

function stringOrNumber(x): string | number {
  return x ? '' : 0
}

// input 是 string 和 number 的聯合類型
// 即 string | number
const input = stringOrNumber(1)

toString('hello') // ok
toString(2) // ok
toString(input) // error
複製代碼

類型推斷

ts 中的類型推斷是很是強大,並且其內部實現也是很是複雜的。

基本類型推斷:

// ts 推導出 x 是 number 類型
let x = 10
複製代碼

對象類型推斷:

// ts 推斷出 myObj 的類型:myObj: { x: number; y: string; z: boolean; }
const myObj = {
  x: 1,
  y: '2',
  z: true
}
複製代碼

函數類型推斷:

// ts 推導出函數返回值是 number 類型
function len (str: string) {
  return str.length
}
複製代碼

上下文類型推斷:

// ts 推導出 event 是 ProgressEvent 類型
const xhr = new XMLHttpRequest()
xhr.onload = function (event) {}
複製代碼

因此有時候對於一些簡單的類型能夠不用手動聲明其類型,讓 ts 本身去推斷。

類型兼容性

typescript 的子類型是基於 結構子類型 的,只要結構能夠兼容,就是子類型。(Duck Type)

class Point {
  x: number
}

function getPointX(point: Point) {
  return point.x
}

class Point2 {
  x: number
}

let point2 = new Point2()

getPointX(point2) // OK
複製代碼

javac++ 等傳統靜態類型語言是基於 名義子類型 的,必須顯示聲明子類型關係(繼承),才能夠兼容。

public class Main {
  public static void main (String[] args) {
    getPointX(new Point()); // ok
    getPointX(new ChildPoint()); // ok
    getPointX(new Point1());  // error
  }

  public static void getPointX (Point point) {
    System.out.println(point.x);
  }

  static class Point {
    public int x = 1;
  }

  static class Point2 {
    public int x = 2;
  }
    
  static class ChildPoint extends Point {
    public int x = 3;
  }
}
複製代碼

對象子類型

子類型中必須包含源類型全部的屬性和方法:

function getPointX(point: { x: number }) {
  return point.x
}

const point = {
	x: 1,
  y: '2'
}

getPointX(point) // OK
複製代碼

注意: 若是直接傳入一個對象字面量是會報錯的:

function getPointX(point: { x: number }) {
  return point.x
}

getPointX({ x: 1, y: '2' }) // error
複製代碼

這是 ts 中的另外一個特性,叫作:  excess property check  ,當傳入的參數是一個對象字面量時,會進行額外屬性檢查。

函數子類型

介紹函數子類型前先介紹一下逆變協變的概念,逆變協變並非 TS 中獨有的概念,在其餘靜態語言中也有相關理念。

在介紹以前,先假設一個問題,約定以下標記:

  • A ≼ B 表示 A 是 B 的子類型,A 包含 B 的全部屬性和方法。
  • A => B 表示以 A 爲參數,B 爲返回值的方法。 (param: A) => B

若是咱們如今有三個類型 AnimalDogWangCai(旺財) ,那麼確定存在下面的關係:

WangCai ≼ Dog ≼ Animal // 即旺財屬於狗屬於動物
複製代碼

問題:如下哪一種類型是 Dog => Dog 的子類呢?

  • WangCai => WangCai
  • WangCai => Animal
  • Animal  => Animal
  • Animal  => WangCai

從代碼來看解答

class Animal {
  sleep: Function
}

class Dog extends Animal {
  // 吠
  bark: Function
}

class WangCai extends Dog {
  dance: Function
}


function getDogName (cb: (dog: Dog) => Dog) {
  const dog = cb(new Dog())
  dog.bark()
}

// 對於入參來講,WangCai 是 Dog 的子類,Dog 類上沒有 dance 方法, 產生異常。
// 對於出參來講,WangCai 類繼承了 Dog 類,確定會有 bark 方法
getDogName((wangcai: WangCai) => {
  wangcai.dance()
  return new WangCai()
})

// 對於入參來講,WangCai 是 Dog 的子類,Dog 類上沒有 dance 方法, 產生異常。
// 對於出參來講,Animal 類上沒有 bark 方法, 產生異常。
getDogName((wangcai: WangCai) => {
  wangcai.dance()
  return new Animal()
})

// 對於入參來講,Animal 類是 Dog 的父類,Dog 類確定有 sleep 方法。
// 對於出參來講,WangCai 類繼承了 Dog 類,確定會有 bark 方法
getDogName((animal: Animal) => {
  animal.sleep()
  return new WangCai()
})

// 對於入參來講,Animal 類是 Dog 的父類,Dog 類確定有 sleep 方法。
// 對於出參來講,Animal 類上沒有 bark 方法, 產生異常。
getDogName((animal: Animal) => {
  animal.sleep()
  return new Animal()
})
複製代碼

能夠看到只有 Animal => WangCai 纔是 Dog => Dog 的子類型,能夠獲得一個結論,對於函數類型來講,函數參數的類型兼容是反向的,咱們稱之爲 逆變 ,返回值的類型兼容是正向的,稱之爲 協變

逆變與協變的例子只說明瞭函數參數只有一個時的狀況,若是函數參數有多個時該如何區分?

其實函數的參數能夠轉化爲 Tuple 的類型兼容性:

type Tuple1 = [string, number]
type Tuple2 = [string, number, boolean]

let tuple1: Tuple1 = ['1', 1]
let tuple2: Tuple2 = ['1', 1, true]

let t1: Tuple1 = tuple2 // ok
let t2: Tuple2 = tuple1 // error
複製代碼

能夠看到 Tuple2 => Tuple1 ,即長度大的是長度小的子類型,再因爲函數參數的逆變特性,因此函數參數少的能夠賦值給參數多的(參數從前日後需一一對應),從數組的 forEach 方法就能夠看出來:

[1, 2].forEach((item, index) => {
	console.log(item)
}) // ok

[1, 2].forEach((item, index, arr, other) => {
	console.log(other)
}) // error
複製代碼

高級類型

聯合類型與交叉類型

聯合類型(union type)表示多種類型的 「或」 關係

function genLen(x: string | any[]) {
  return x.length
}

genLen('') // ok
genLen([]) // ok
genLen(1) // error
複製代碼

交叉類型表示多種類型的 「與」 關係

interface Person {
  name: string
  age: number
}

interface Animal {
  name: string
  color: string
}

const x: Person & Animal = {
  name: 'x',
  age: 1,
  color: 'red
}
複製代碼

使用聯合類型表示枚舉

type Position = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'

const position: Position = 'UP'
複製代碼

能夠避免使用 enum 侵入了運行時。

類型保護

ts 初學者很容易寫出下面的代碼:

function isString (value) {
  return Object.prototype.toString.call(value) === '[object String]'
}

function fn (x: string | number) {
  if (isString(x)) {
    return x.length // error 類型「string | number」上不存在屬性「length」。
  } else {
    // .....
  }
}
複製代碼

如何讓 ts 推斷出來上下文的類型呢?

1. 使用 ts 的 is 關鍵詞

function isString (value: unknown): value is string {
  return Object.prototype.toString.call(value) === '[object String]'
}

function fn (x: string | number) {
  if (isString(x)) {
    return x.length
  } else {
    // .....
  }
}
複製代碼

2. typeof 關鍵詞

在 ts 中,代碼實現中的 typeof 關鍵詞可以幫助 ts 判斷出變量的基本類型:

function fn (x: string | number) {
  if (typeof x === 'string') { // x is string
    return x.length
  } else { // x is number
    // .....
  }
}
複製代碼

3. instanceof 關鍵詞

在 ts 中,instanceof 關鍵詞可以幫助 ts 判斷出構造函數的類型:

function fn1 (x: XMLHttpRequest | string) {
  if (x instanceof XMLHttpRequest) { // x is XMLHttpRequest
    return x.getAllResponseHeaders()
  } else { // x is string
    return x.length
  }
}
複製代碼

4. 針對 null 和 undefined 的類型保護

在條件判斷中,ts 會自動對 null 和 undefined 進行類型保護:

function fn2 (x?: string) {
  if (x) {
    return x.length
  }
}
複製代碼

5. 針對 null 和 undefined 的類型斷言

若是咱們已經知道的參數不爲空,可使用 ! 來手動標記:

function fn2 (x?: string) {
  return x!.length
}
複製代碼

typeof 關鍵詞

typeof 關鍵詞除了作類型保護,還能夠從實現推出類型,

注意:此時的 typeof 是一個類型關鍵詞,只能夠用在類型語法中。

function fn(x: string) {
  return x.length
}

const obj = {
  x: 1,
  y: '2'
}

type T0 = typeof fn // (x: string) => number
type T1 = typeof obj // {x: number; y: string }
複製代碼

keyof 關鍵詞

keyof 也是一個 類型關鍵詞 ,能夠用來取得一個對象接口的全部 key 值:

interface Person {
  name: string
  age: number
}

type PersonAttrs = keyof Person // 'name' | 'age'
複製代碼

in 關鍵詞

in 也是一個 類型關鍵詞, 能夠對聯合類型進行遍歷,只能夠用在 type 關鍵詞下面。

type Person = {
  [key in 'name' | 'age']: number
}

// { name: number; age: number; }
複製代碼

[ ] 操做符

使用 [] 操做符能夠進行索引訪問,也是一個 類型關鍵詞

interface Person {
  name: string
  age: number
}

type x = Person['name'] // x is string
複製代碼

一個小栗子

寫一個類型複製的類型工具:

type Copy<T> = {
  [key in keyof T]: T[key]
}

interface Person {
  name: string
  age: number
}

type Person1 = Copy<Person>
複製代碼

泛型

泛型至關於一個類型的參數,在 ts 中,泛型能夠用在 接口方法類型別名 等實體中。

小試牛刀

function createList<T>(): T[] {
  return [] as T[]
}

const numberList = createList<number>() // number[]
const stringList = createList<string>() // string[]
複製代碼

有了泛型的支持,createList 方法能夠傳入一個類型,返回有類型的數組,而不是一個 any[]

泛型約束

若是咱們只但願 createList 函數只能生成指定的類型數組,該如何作,可使用 extends 關鍵詞來約束泛型的範圍和形狀。

type Lengthwise = {
  length: number
}

function createList<T extends string | Lengthwise>(): T[] {
  return [] as T[]
}

const numberList = createList<number>() // ok
const arrayList = createList<any[]>() // ok
const stringList = createList<boolean>() // error
複製代碼

由於 any[] 是一個數組類型,數組類型是有 length 參數的,因此 ok。

條件控制

extends 除了作約束類型,還能夠作條件控制,至關於與一個三元運算符,只不過是針對 類型 的。

T extends U ? X : Y

若是 T 能夠被分配給 U,則返回 X,不然返回 Y。通常條件下,若是 T 是 U 的子類型,則認爲 T 能夠分配給 U。

type IsNumber<T> = T extends number ? true : false

type x = IsNumber<string>  // false
複製代碼

映射類型

映射類型至關與一個類型的函數,能夠作一些類型運算,輸入一個類型,輸出另外一個類型,前文咱們舉了個 Copy 的例子。

幾個內置的映射類型

// 每個屬性都變成可選
type Partial<T> = {
  [P in keyof T]?: T[P]
}

// 每個屬性都變成只讀
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

// 選擇對象中的某些屬性
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
}

// ......
複製代碼

typescript 2.8 在 lib.d.ts 中內置了幾個映射類型:

  • Partial<T> -- 將 T 中的全部屬性變成可選。
  • Readonly<T> -- 將 T 中的全部屬性變成只讀。
  • Pick<T, U> -- 選擇 T 中能夠賦值給U的類型。
  • Exclude<T, U> -- 從T中剔除能夠賦值給U的類型。
  • Extract<T, U> -- 提取T中能夠賦值給U的類型。
  • NonNullable<T> -- 從T中剔除nullundefined
  • ReturnType<T> -- 獲取函數返回值類型。
  • InstanceType<T> -- 獲取構造函數類型的實例類型。

因此咱們平時寫 TS 時能夠直接使用這些類型工具:

interface ApiRes {
  code: string;
  flag: string;
  message: string;
  data: object;
  success: boolean;
  error: boolean;
}

type IApiRes = Pick<ApiRes, 'code' | 'flag' | 'message' | 'data'>

// {
// code: string;
// flag: string;
// message: string;
// data: object;
// }
複製代碼

extends 條件分發

對於 T extends U ? X : Y 來講,還存在一個特性,當 T 是一個聯合類型時,會進行條件分發。

type Union = string | number
type isNumber<T> = T extends number ? 'isNumber' : 'notNumber'

type UnionType = isNumber<Union> // 'notNumber' | 'isNumber'
複製代碼

實際上,extends 運算會變成以下形式:

(string extends number ? 'isNumber' : 'notNumber') | (number extends number ? 'isNumber' : 'notNumber')
複製代碼

Extract 就是基於此特性,再配合 never 幺元的特性實現的:

type Exclude<T, K> = T extends K ? never : T

type T1 = Exclude<string | number | boolean, string | boolean>  // number
複製代碼

infer 關鍵詞

infer 能夠對運算過程當中的類型進行存儲,內置的ReturnType 就是基於此特性實現的:

type ReturnType<T> = 
  T extends (...args: any) => infer R ? R : never

type Fn = (str: string) => number

type FnReturn = ReturnType<Fn> // number
複製代碼

模塊

全局模塊 vs. 文件模塊

默認狀況下,咱們所寫的代碼是位於全局模塊下的:

const foo = 2
複製代碼

此時,若是咱們建立了另外一個文件,並寫下以下代碼,ts 認爲是正常的:

const bar = foo // ok
複製代碼

若是要打破這種限制,只要文件中有 import 或者 export 表達式便可:

export const bar = foo // error
複製代碼

模塊解析策略

Tpescript 有兩種模塊的解析策略:NodeClassic。當 tsconfig.json 中 module 設置成 AMD、System、ES2015 時,默認爲 classic ,不然爲 Node ,也可使用 moduleResolution  手動指定模塊解析策略。

兩種模塊解析策略的區別在於,對於下面模塊引入來講:

import moduleB from 'moduleB'
複製代碼

Classic 模式的路徑尋址:

/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
/root/src/moduleB.ts
/root/src/moduleB.d.ts
/root/moduleB.ts
/root/moduleB.d.ts
/moduleB.ts
/moduleB.d.ts
複製代碼

Node 模式的路徑尋址:

/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json (若是指定了"types"屬性)
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts

/root/node_modules/moduleB.ts
/root/node_modules/moduleB.tsx
/root/node_modules/moduleB.d.ts
/root/node_modules/moduleB/package.json (若是指定了"types"屬性)
/root/node_modules/moduleB/index.ts
/root/node_modules/moduleB/index.tsx
/root/node_modules/moduleB/index.d.ts

/node_modules/moduleB.ts
/node_modules/moduleB.tsx
/node_modules/moduleB.d.ts
/node_modules/moduleB/package.json (若是指定了"types"屬性)
/node_modules/moduleB/index.ts
/node_modules/moduleB/index.tsx
/node_modules/moduleB/index.d.ts
複製代碼

聲明文件

什麼是聲明文件

聲明文件已 .d.ts 結尾,用來描述代碼結構,通常用來爲 js 庫提供類型定義。

平時開發的時候有沒有這種經歷:當用npm安裝了某些包並使用的時候,會出現這個包的語法提示,下面是 vue 的提示:

這個語法提示就是聲明文件的功勞了,先來看一個簡單的聲明文件長啥樣,這是jsonp這個庫的聲明文件:

type CancelFn = () => void;
type RequestCallback = (error: Error | null, data: any) => void;

interface Options {
    param?: string;
    prefix?: string;
    name?: string;
    timeout?: number;
}

declare function jsonp(url: string, options?: Options, cb?: RequestCallback): CancelFn;
declare function jsonp(url: string, callback?: RequestCallback): CancelFn;

export = jsonp;
複製代碼

有了這份聲明文件,編輯器在使用這個庫的時候就能夠根據這份聲明文件來作出相應的語法提示。

編輯器是怎麼找到這個聲明文件?

  • 若是這個包的根目錄下有一個index.d.ts,那麼這就是這個庫的聲明文件了。
  • 若是這個包的package.json中有types或者typings字段,那個該字段指向的就是這個包的聲明文件。

上述兩種都是將聲明文件寫在包裏面的狀況,若是某個庫很長時間不維護了,或者做者消失了該怎麼辦,不要緊,typescript官方提供了一個聲明文件倉庫,嘗試使用@types前綴來安裝某個庫的聲明文件:

npm i @types/lodash
複製代碼

當引入lodash的時候,編輯器也會嘗試查找node_modules/@types/lodash 來爲你提供lodash的語法提示。

還有一種就是本身寫聲明文件,編輯器會收集項目本地的聲明文件,若是某個包沒有聲明文件,你又想要語法提示,就能夠本身在本地寫個聲明文件:

// types/lodash.d.ts
declare module "lodash" {
  export function chunk(array: any[], size?: number): any[];
  export function get(source: any, path: string, defaultValue?: any): any;
}
複製代碼

若是源代碼是用ts寫的,在編譯成js的時候,只要加上-d 參數,就能生成對應的聲明文件。

tsc -d
複製代碼

聲明文件該怎麼寫能夠參考www.tslang.cn/docs/handbo…

還要注意的是,若是某個庫有聲明文件了,編輯器就不會再關心這個庫具體的代碼了,它只會根據聲明文件來作提示。

擴展原生對象

可能寫過 ts 的小夥伴有這樣的疑惑,我該如何在 window 對象上自定義屬性呢?

window.myprop = 1 // error
複製代碼

默認的,window 上是不存在 myprop 這個屬性的,因此不能夠直接賦值,固然,能夠輸用方括號賦值語句,可是 get 操做時也必須用 [] ,而且沒有類型提示。

window['myprop'] = 1 // OK

window.myprop  // 類型「Window & typeof globalThis」上不存在屬性「myprop」
window['myprop'] // ok,可是沒有提示,沒有類型
複製代碼

此時可使用聲明文件擴展其餘對象,在項目中隨便建一個xxx.d.ts

// index.d.ts
interface Window {
  myprop: number
}

// index.ts
window.myprop = 2  // ok
複製代碼

也能夠在模塊內部擴展全局對象:

import A from 'moduleA'

window.myprop = 2

declare global {
  interface Window {
    myprop: number
  }
}
複製代碼

擴展其餘模塊

若是使用過 ts 寫過 vue  的同窗,必定都碰到過這個問題,如何擴展 vue.prototype 上的屬性或者方法?

import Vue from 'vue'

Vue.prototype.myprops = 1

const vm = new Vue({
  el: '#app'
})

// 類型「CombinedVueInstance<Vue, object, object, object, Record<never, any>>」
// 上不存在屬性「myprops」
console.log(vm.myprops)
複製代碼

vue 給出的方案,在項目中的 xxx.d.ts 中擴展 vue 實例上的屬性:

import Vue from 'vue'

declare module 'vue/types/vue' {
  interface Vue {
    myprop: number
  }
}
複製代碼

ts 提供了 declare module 'xxx' 的語法來擴展其餘模塊,這很是有利於一些插件化的庫和包,例如 vue-router 擴展 vue

// vue-router/types/vue.d.ts
import Vue from 'vue'
import VueRouter, { Route, RawLocation, NavigationGuard } from './index'

declare module 'vue/types/vue' {
  interface Vue {
    $router: VueRouter
    $route: Route
  }
}

declare module 'vue/types/options' {
  interface ComponentOptions<V extends Vue> {
    router?: VueRouter
    beforeRouteEnter?: NavigationGuard<V>
    beforeRouteLeave?: NavigationGuard<V>
    beforeRouteUpdate?: NavigationGuard<V>
  }
}
複製代碼

如何處理非 js 文件,例如 .vue 文件引入?

處理 vue 文件

對於全部以 .vue 結尾的文件,能夠默認導出 Vue 類型,這是符合 vue單文件組件 的規則的。

declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}
複製代碼

處理 css in js

對於全部的 .css,能夠默認導出一個 any 類型的值,這樣能夠解決報錯問題,可是丟失了類型檢查。

declare module '*.css' {
	const content: any
  export default content
}
複製代碼
import * as React from 'react'
import * as styles from './index.css'

const Error = () => (
    <div className={styles.centered}>
        <div className={styles.emoji}>😭</div>
        <p className={styles.title}>Ooooops!</p>
        <p>This page doesn't exist anymore.</p>
    </div>
)

export default Error
複製代碼

其實不論是全局擴展仍是模塊擴展,其實都是基於 TS 聲明合併 的特性,簡單來講,TS 會將它收集到的一些同名的接口、類、類型別名按照必定的規則進行合併。

編譯

ts 內置了一個 compiler (tsc),可讓咱們把 ts 文件編譯成 js 文件,配合衆多的編譯選項,有時候不須要 babel  咱們就能夠完成大多數工做。

經常使用的編譯選項

tsc 在編譯 ts 代碼的時候,會根據 tsconfig.json 配置文件的選項採起不一樣的編譯策略。下面是三個經常使用的配置項:

  • target - 生成的代碼的JS語言的版本,好比ES三、ES五、ES2015等。
  • module - 生成的代碼所須要支持的模塊系統,好比 es201五、commonjs、umd等。
  • lib - 告訴TS目標環境中有哪些特性,好比 WebWorker、ES201五、DOM等。

babel 同樣,ts 在編譯的時候只會轉化新 語法,不會轉化新的 API, 因此有些場景下須要自行處理 polyfill 的問題。

更改編譯後的目錄

tsconfig 中的 outDir 字段能夠配置編譯後的文件目錄,有利於 dist 的統一管理。

{
  "compilerOptions": {
    "module": "umd",
    "outDir": "./dist"
  }
}
複製代碼

編譯後的目錄結構:

myproject
├── dist
│   ├── index.js
│   └── lib
│       └── moduleA.js
├── index.ts
├── lib
│   └── moduleA.ts
└── tsconfig.json
複製代碼

編譯後輸出到一個js文件中

對於 amdsystem 模塊,能夠配置 tsconfig.json 中的 outFile 字段,輸出爲一個 js 文件。
若是須要輸出成其餘模塊,例如 umd ,又但願打包成一個單獨的文件,須要怎麼作?
可使用 rollup 或者 webpack

// rollup.config.js
const typescript = require('rollup-plugin-typescript2')

module.exports = {
  input: './index.ts',
  output: {
    name: 'MyBundle',
    file: './dist/bundle.js',
    format: 'umd'
  },
  plugins: [
    typescript()
  ]
}
複製代碼

一些經常使用的 ts 周邊庫

一個提升開發效率的小技巧

你們在平常開發的時候,可能會常常用到webpack的路徑別名,好比: import xxx from '@/path/to/name',若是編輯器不作任何配置的話,這樣寫會很尷尬,編譯器不會給你任何路徑提示,更不會給你語法提示。這裏有個小技巧,基於 tsconfig.jsonbaseUrlpaths這兩個字段,配置好這兩個字段後,.ts文件裏不但有了路徑提示,還會跟蹤到該路徑進行語法提示。

這裏有個小彩蛋,能夠把 tsconfig.json 重命名成jsconfig.json.js文件裏也能享受到路徑別名提示和語法提示了。

使用 webstorm 的同窗若是也想使用的話,只要打開設置,搜索webpack,而後設置一下webpack配置文件的路徑就行了。

學習推薦

相關文章
相關標籤/搜索