TS核心知識(shí)點(diǎn)總結(jié),帶你半小時(shí)入門 TS
前言
最近工作一直很忙,復(fù)盤周期也有所拉長(zhǎng),不過(guò)還是會(huì)堅(jiān)持每周復(fù)盤。今天筆者將復(fù)盤一下typescript在前端項(xiàng)目中的應(yīng)用,至于為什么要學(xué)習(xí)typescript,我想大家也不言自明,目前主流框架vue和react以及相關(guān)生態(tài)的內(nèi)部構(gòu)建大部分都采用了typescript,其原因就在于它的靜態(tài)類型檢查極大的提高了代碼的可讀性和可維護(hù)性,而且定位問(wèn)題非常方便。下面上一份關(guān)于typescript的官方定義,方便大家理解:
TypeScript 是由微軟開(kāi)發(fā)的自由和開(kāi)源的編程語(yǔ)言, 是JavaScript 的一個(gè)超集,支持 ECMAScript 6 標(biāo)準(zhǔn)。其設(shè)計(jì)目標(biāo)是開(kāi)發(fā)大型應(yīng)用,它可以編譯成純 JavaScript,編譯出來(lái)的 JavaScript 可以運(yùn)行在任何瀏覽器上。
概要
任何語(yǔ)言的學(xué)習(xí)都要有學(xué)習(xí)和思考體系,前端也不例外,筆者將按照如下圖所示結(jié)構(gòu)來(lái)進(jìn)行講解:

正文
我們目前項(xiàng)目開(kāi)發(fā)用的最多的就是webpack,對(duì)于ts,我們也很方便的可以通過(guò)ts-loader對(duì)其進(jìn)行編譯配置,為了降低大家學(xué)習(xí)ts的難度,筆者推薦采用vue-cli3或者umi直接搭建ts項(xiàng)目,這樣可以更快的上手ts開(kāi)發(fā)。
核心知識(shí)點(diǎn)
TypeScript支持與JavaScript幾乎相同的數(shù)據(jù)類型,此外還提供了實(shí)用的枚舉類型方便我們使用。接下來(lái)我們簡(jiǎn)單介紹一下這幾種類型的用法.
// 布爾類型
let isCookie:boolean = true
// 數(shù)值類型
let myMoney:number = 12
// 字符串類型
let name:string = '徐小夕'
// 數(shù)組類型, 有兩種表示方式,第一種可以在元素類型后面接上[],表示由此類型元素組成的一個(gè)數(shù)組
let arr:number[] = [1,2,2]
// 數(shù)組類型, 使用數(shù)組泛型
let arr:Array<number> = [1,2,2]
// 元組類型, 允許表示一個(gè)已知元素?cái)?shù)量和類型的數(shù)組,各元素的類型不必相同
let xi: [string, number];
// 初始化xi
xi = ['xu', 10]; // 正確
xi = [11, 'xu']; // 錯(cuò)誤
// 枚舉類型, 可以為一組數(shù)值賦予友好的名字
enum ActionType { doing, done, fail }
let action:ActionType = ActionType.done // 1
// any, 表示任意類型, 可以繞過(guò)類型檢查器對(duì)這些值進(jìn)行檢查
let color:any = 1
color = 'red'
// void類型, 當(dāng)一個(gè)函數(shù)沒(méi)有返回值時(shí),通常會(huì)設(shè)置其返回值類型是 void
function getName(): void {
console.log("This is my name");
}
// object類型, 表示非原始類型,也就是除number,string,boolean,symbol,null或undefined之外的類型
let a:object;
a = {props: 1}復(fù)制代碼
以上是typescript中常用的幾種類型, 也是我們必須掌握的基本知識(shí). 這里值得補(bǔ)充的是typescript的類型斷言, 也是解決ts警告的利器,比如我們確切的知道某種數(shù)據(jù)的數(shù)據(jù)類型,我們可以這么做:
let arrLen: number = (someValue as Array ).length;
// 解決window下設(shè)置屬性的ts報(bào)錯(cuò), 但不可濫用
(window as any).name = 'xuxi'復(fù)制代碼
TypeScript的核心原則之一是對(duì)值所具有的結(jié)構(gòu)進(jìn)行類型檢查。?在TypeScript里,接口的作用就是為這些類型命名和為你的代碼或第三方代碼定義契約。接下來(lái)我們看看如何定義和使用接口(Interface):
interface Product {
name: string;
size: number;
weight: number;
}
let product1:Product = {
name: 'machine1',
size: 20,
weight: 10.5
}復(fù)制代碼
類型檢查器不會(huì)去檢查屬性的順序,只要相應(yīng)的屬性存在并且類型也是對(duì)的就可以。其次我們還可以定義可選屬性和只讀屬性.?可選屬性表示了接口里的某些屬性不是必需的,所以可以定義也可以不定義.可讀屬性使得接口中的某些屬性只能讀取而不能賦值. 具體案例如下:
interface Product {
name: string;
size?: number;
readonly weight: number;
}復(fù)制代碼
在實(shí)際場(chǎng)景中, 我們往往還會(huì)遇到不確定屬性名和屬性值類型的情況, 這種情況往往發(fā)生在第三發(fā)SDK接入或者后端響應(yīng)中, 這個(gè)時(shí)候我們可以利用索引簽名來(lái)設(shè)置額外的屬性和類型, 案例如下:
interface App {
name: string;
color?: number;
[propName: string]: any;
}復(fù)制代碼
接口除了描述帶有屬性的普通對(duì)象外,也可以描述函數(shù)類型。我們需要給接口定義一個(gè)調(diào)用簽名, 參數(shù)列表里的每個(gè)參數(shù)都需要名字和類型。案例如下:
interface MyFunc {
(value:string, type: number): boolean;
}
// 使用
let myLoveFront: MyFunc;
myLoveFront = function(value:string, type: number) {
return type > 1
}復(fù)制代碼
我們?cè)?span style="font-weight: 700;">vue和react開(kāi)發(fā)中,也會(huì)經(jīng)常使用class這種類來(lái)編寫可復(fù)用組件和庫(kù), 既然ts可以描述函數(shù)類型, 那么是不是也可以用來(lái)描述類類型呢? 答案是可以的.但是類接口的定義稍微有點(diǎn)復(fù)雜, 我們都知道類是具有兩個(gè)類型的:靜態(tài)部分的類型和實(shí)例的類型. 當(dāng)一個(gè)類實(shí)現(xiàn)了一個(gè)接口時(shí),只對(duì)其實(shí)例部分進(jìn)行類型檢查。constructor存在于類的靜態(tài)部分,所以不在檢查的范圍內(nèi)。. 這句話相當(dāng)關(guān)鍵, 我們?cè)诙x類接口的時(shí)候也要主要這一特點(diǎn), 案例如下:
interface TickConstructor {
new (hour: number, minute: number): TickInterface;
}
interface TickInterface {
tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): TickInterface {
return new ctor(hour, minute);
}
class DigitalClock implements TickInterface {
constructor(h: number, m: number) { }
tick() {
console.log("xu xu");
}
}
class MyTick implements TickInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalTick, 12, 17);
let analog = createClock(MyTick, 7, 32);復(fù)制代碼
關(guān)于類接口的話題我們?cè)谏衔囊呀?jīng)介紹了, 這里我們來(lái)具體了解一下類. 和js的class一致, typescript的類有公共,私有與受保護(hù)的修飾符. 具體含義如下:
public?在TypeScript里,成員都默認(rèn)為 public,我們可以自由的訪問(wèn)程序里定義的成員 private?當(dāng)成員被標(biāo)記成 private時(shí),它就不能在聲明它的類的外部訪問(wèn) protected?和private類似, 但是protected成員在派生類中仍然可以訪問(wèn)
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name)
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}復(fù)制代碼
抽象類做為其它派生類的基類使用。它們一般不會(huì)直接被實(shí)例化。不同于接口,抽象類可以包含成員的實(shí)現(xiàn)細(xì)節(jié)。abstract關(guān)鍵字是用于定義抽象類和在抽象類內(nèi)部定義抽象方法。
abstract class MyAbstract {
constructor(public name: string) {}
say(): void {
console.log('say name: ' + this.name);
}
abstract sayBye(): void; // 必須在派生類中實(shí)現(xiàn)
}
class AccountingMyAbstract extends MyAbstract {
constructor() {
super('小徐'); // 在派生類的構(gòu)造函數(shù)中必須調(diào)用 super()
}
sayBye(): void {
console.log('趣談小夕.');
}
getOther(): void {
console.log('loading...');
}
}
let department: MyAbstract; // 允許創(chuàng)建一個(gè)對(duì)抽象類型的引用
department = new MyAbstract(); // 錯(cuò)誤: 不能創(chuàng)建一個(gè)抽象類的實(shí)例
department = new AccountingMyAbstract(); // 允許對(duì)一個(gè)抽象子類進(jìn)行實(shí)例化和賦值
department.say();
department.sayBye();
department.getOther(); // 錯(cuò)誤: 方法在聲明的抽象類中不存在復(fù)制代碼
函數(shù)類型在上文已經(jīng)介紹過(guò)了, 這里主要在講一下可選參數(shù)這個(gè)概念. JavaScript里每個(gè)參數(shù)都是可選的,可傳可不傳。沒(méi)傳參的時(shí)候其值就是undefined。在TypeScript里我們可以在參數(shù)名旁使用 ?實(shí)現(xiàn)可選參數(shù)的功能。具體案例如下:
function createName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}復(fù)制代碼
我們可以使用泛型來(lái)創(chuàng)建可重用的組件,一個(gè)組件可以支持多種類型的數(shù)據(jù)。這樣用戶就可以以自己的數(shù)據(jù)類型來(lái)使用組件。泛型是typescript中比較難懂的知識(shí)點(diǎn), 但是非常重要, 幾乎任何第三方組件庫(kù)里都會(huì)用到. 我們先來(lái)看個(gè)最簡(jiǎn)單的例子:
function iSay<T>(arg: T): T {
return arg;
}
// 調(diào)用泛型函數(shù)
let come = iSay<number>(123);復(fù)制代碼
我們給iSay添加了類型變量T。T幫助我們捕獲用戶傳入的類型(比如:string),這樣我們就可以使用這個(gè)類型。之后我們?cè)俅问褂肨當(dāng)做返回值類型。現(xiàn)在我們可以知道參數(shù)類型與返回值類型是相同的了。這允許我們跟蹤函數(shù)里使用的類型的信息。
我們還可以把泛型變量T當(dāng)做類型的一部分使用,而不是整個(gè)類型, 這樣可以增加我們的使用靈活性, 案例如下:
function iSay<T>(arg: T[]): T[] {
console.log(arg.length)
return arg;
}復(fù)制代碼
類似于函數(shù)類型的定義, 我們也可以定義泛型接口, 并且可以把泛型參數(shù)當(dāng)作整個(gè)接口的一個(gè)參數(shù), 這樣我們就能清楚的知道使用的具體是哪個(gè)泛型類型. 案例代碼如下:
interface SayLove {
(arg: T): T
}
// 把泛型參數(shù)當(dāng)作整個(gè)接口的一個(gè)參數(shù)
interface SayLoveArg{
(arg: T): T
}
// 泛型函數(shù)
function iSay<T>(arg: T): T {
return arg;
let mySay1:SayLove = iSay
let mySay2:SayLoveArg<number> = iSay復(fù)制代碼
同樣的我們還可以定義泛型類.我們只需要使用(<>)括起泛型類型,跟在類名后面即可. 具體案例如下:
class MyNumber {
year: T;
compute: (x: T, y: T) => T;
}
let myGenericNumber = new MyNumber<number>();復(fù)制代碼
interface NumberControl {
length: number
}
class MyObjectextends NumberControl>(arg: T):T {
console.log(arg.length)
return arg
}復(fù)制代碼
typescript的高級(jí)類型里我們主要講解如下核心知識(shí)點(diǎn):
交叉類型 聯(lián)合類型 多態(tài)的 this類型 索引類型查詢操作符 索引訪問(wèn)操作符
function extend<T, U>(first: T, second: U): T & U {
let result ={};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}復(fù)制代碼
我們通過(guò)字符?&?來(lái)表示聯(lián)合, 此時(shí)以上代碼中的返回值會(huì)具有T和U的類型.
聯(lián)合類型表示一個(gè)值可以是幾種類型之一。我們用豎線(|)分隔每個(gè)類型,所以 number | string | boolean表示一個(gè)值可以是 number, string,或 boolean。具體例子如下:
let name: string | number = 'xuxiaoxi'
function sayName(name: string):(string|number) {
return name
}復(fù)制代碼
值得注意的是:?如果一個(gè)值是聯(lián)合類型,我們只能訪問(wèn)此聯(lián)合類型的所有類型里共有的成員。
還有一種常見(jiàn)的需求是, 我們?cè)趯?shí)現(xiàn)自己的類后,需要支持類方法的鏈?zhǔn)秸{(diào)用, 這個(gè)時(shí)候我們應(yīng)該返回this, 在typescript中我們就需要了解多態(tài)的 this類型. 它表示的是某個(gè)包含類或接口的子類型。這被稱做 F-bounded多態(tài)性。要想在typescript中支持鏈?zhǔn)? 我們可以這么寫:
class MyCalculator {
public constructor(number = 0) { }
public add(n: number): this {
this.value += n;
return this;
}
public multiply(n: number): this {
this.value *= n;
return this;
}
// ... 其他操作 ...
}
let v = new MyCalculator(2).multiply(5).add(1);復(fù)制代碼
下面一個(gè)我們需要知道的知識(shí)點(diǎn)是索引類型查詢操作符. 一般用keyof表示。對(duì)于任何類型T, keyof T的結(jié)果為T上已知的公共屬性名的聯(lián)合。比如我們定義一個(gè)接口Animal:
interface Animal {
cat: string;
dog: string;
}
let AnimalProps: keyof Animal; // 'cat' | 'dog'復(fù)制代碼
keyof Animal是完全可以與 'cat' | 'dog'互相替換的。不同的是如果我們添加了其它的屬性到 Animal,例如 pig: string,那么 keyof Animal會(huì)自動(dòng)變?yōu)?'cat' | 'dog' | 'pig'。
命名空間主要作用是用來(lái)組織代碼,以便于在記錄它們類型的同時(shí)還不用擔(dān)心與其它對(duì)象產(chǎn)生命名沖突。由于命名空間的用法很簡(jiǎn)單,這里我們以網(wǎng)上比較流行的D3作為例子, 代碼如下:
declare namespace D3 {
export interface Selectors {
select: {
(selector: string): Selection;
(element: EventTarget): Selection;
};
}
export interface Event {
x: number;
y: number;
}
export interface Base extends Selectors {
event: Event;
}
}
declare var d3: D3.Base;復(fù)制代碼
在熟悉以上基礎(chǔ)知識(shí)之后, 我們看一下如何使用支持typescript的第三方類庫(kù). 比如說(shuō)我們常用的lodash, 那么正確的使用步驟如下:
// 安裝lodash和對(duì)應(yīng)的類型文件
npm install --save lodash @types/lodash
// 在代碼中使用
import * as _ from "lodash";
_.padStart("Hello xuxiaoxi!", 12, " ");復(fù)制代碼
聲明文件也是一個(gè)非常重要的知識(shí)點(diǎn).對(duì)于使用未經(jīng)聲明的全局函數(shù)或者全局變量, typescript往往會(huì)報(bào)錯(cuò), 所以我們可以在對(duì)應(yīng)位置添加xxx.d.ts文件, 并在里面聲明我們所需要的變量, ts會(huì)自動(dòng)檢索到該文件并進(jìn)行解析. 以下是幾個(gè)案例, 供大家參考學(xué)習(xí):
// global.d.ts
// 聲明全局變量
declare var name: string;
// 全局函數(shù)
declare function say(name: string): void;
// 帶屬性的對(duì)象
declare namespace myObj {
function say(s: string): string;
let money: number;
}
// 可重用的接口
interface Animal {
kind: string;
age: number;
weight?: number;
}
declare function findAnmiate(animal:Animal):void復(fù)制代碼
React + Typescript項(xiàng)目實(shí)戰(zhàn)
為了幫助大家快速上手typescript開(kāi)發(fā), 這里我們采用umi來(lái)搭建一個(gè)支持ts的項(xiàng)目, 不熟悉的朋友可以參考筆者之前學(xué)習(xí)umi的文章.
我們?cè)陧?xiàng)目src目錄下創(chuàng)建一個(gè)global.d.ts來(lái)作為我們?nèi)值穆暶魑募? 用來(lái)處理全局聲明和兼容第三方庫(kù).
我們?cè)趕rc目錄下創(chuàng)建一個(gè)utils目錄用來(lái)存放我們的工具類或者通用庫(kù), 比如在utils下新建一個(gè)tool.ts作為我們的通用工具函數(shù), 這里我們寫個(gè)最簡(jiǎn)單的例子:
/**
* 生成uuid
*/
const?uuid = ():string => {
????let?s:Array<any> = [];
????let?hexDigits:string =?"0123456789abcdef";
????for?(let?i = 0; i < 36; i++) {
????????s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
????}
????s[14] =?"4";?
????s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);?
????s[8] = s[13] = s[18] = s[23] =?"-";
????let?uuid = s.join("");
????return?uuid;
};
// 可以來(lái)自于外部的type.ts文件
interface Params {
[propName: string]: string | number
}
/**
* reverseJson 反轉(zhuǎn)對(duì)象鍵值對(duì)
* @param {object} obj 待反轉(zhuǎn)的對(duì)象
* @param {object} target 反轉(zhuǎn)的目標(biāo)對(duì)象
*/
const reverseJson = (obj:Params = {}, target:Params = {}):Params => {
Object.keys(obj).forEach((key:string) => { target[obj[key]] = key })
return target
}復(fù)制代碼
—————END—————
喜歡本文的朋友,歡迎關(guān)注公眾號(hào)?達(dá)達(dá)前端,收看更多精彩內(nèi)容
點(diǎn)個(gè)[在看],是對(duì)達(dá)達(dá)最大的支持!

