天天看點

前端應該掌握的Typescript基礎知識

TS 介紹

TS 是什麼

js 是一門動态弱類型語言, 我門可以随意的給變量賦不同類型的值

ts 是擁有類型檢查系統的 javascript 超集, 提供了對 es6 的支援, 可以編譯成純 javascript,運作在任何浏覽器上。

TypeScript 編譯工具可以運作在任何伺服器和任何系統上。TypeScript 是開源的。

為什麼要用 TS

ts 總體給我的感覺就是, 它能限制代碼, 又有一定的靈活度, 可以培養你的程式設計習慣, 輸出更高品質, 維護性高, 可讀性高的代碼

  • 編譯代碼時,進行嚴格的靜态類型檢查, 編譯階段而不是運作時發現很多錯誤, 特别是一些很低級的錯誤
  • 幫助我們在寫代碼的時候提供更豐富的文法提示, 友善的檢視定義對象上的屬性和方法

    比如: 你給函數傳了一個對象, 你在函數實作的時候還得記住對象裡面都有啥參數, 你定義的參數名字是啥

TS 安裝

npm init -y npm install typescript -g

編譯

tsc --init tsc

資料類型

js 中的資料類型: 字元串(String)、數字(Number)、布爾(Boolean)、空(Null)、未定義(Undefined)、Symbol。 對象(Object)、數組(Array)、函數(Function)

ts 包含 js 中所有的類型, 而且還新增了幾種類型 void、any、never、元組、枚舉、進階類型

類型注解:

(變量/函數):type

  • 布爾類型(boolean)

let flag: boolean = true

  • 數字類型(number)

let num: number = 8;

  • 字元串類型(string)

let str: string = 'sxh';

  • 數組類型(array)

let arr1: number[] = [1, 2, 3]; let arr2: Array = [1, 2, 3]; // 接口定義數組 interface IArray { [index: number]: number; } let arr: IArray = [1, 1, 2, 3, 5];

隻讀數組 數組建立後不能被修改

let ro: ReadonlyArray = arr1; // arr1.push(3); // ro[1] = 4; // ro.push(6); // ro.length = 100; // arr1 = ro; // let arr3: number[] = ro

  • 元組類型(tuple)

控制了數組成員的類型和數量

let tuple: [string, number] = ['sxh', 18]; // 元組越界問題: tuple.push(2) // 可以添加的類型是所有數組成員的聯合類型 console.log(tuple[2]) // 不能這樣通路

  • 枚舉類型(enum)

普通枚舉

若枚舉類型未指定值或指定的值為number類型, 可對其進行雙向取值

// 雙向取值 enum Color { RED, BLUE, } console.log(Color[0], Color[1]); console.log(Color['RED'], Color['BLUE']); enum Color { RED=2, BLUE, } enum ActionType { ADD = 'ADD', EDIT = 'EDIT', DELETE = 'DELETE', }

常量枚舉

const enum Status { 'success', 'warning', 'fail', } let loginStatus = [Status.success, Status.warning, Status.fail];

keyof typeof Enum, 将枚舉類型轉換為聯合類型

enum ActionType { ADD, EDIT, DELETE, } type ActionTypeConst = keyof typeof ActionType // 'ADD' | 'EDIT' | 'DELETE'

  • null、undefined

    null, undefined 是其他類型的子類型, 可以指派給其他類型的變量

    strictNullChecks 為 true 的話不能指派給其他類型

let str: string; str = null; str = undefined;

  • 任意類型(any)

    任意類型 any 類型 類型轉換困難的時候, 資料類型結構複雜,沒有類型聲明的時候用

    如果變量定義為 any 類型, 跟 js 差不多了, 不進行類型檢查了

  • unkonwn 未知類型

let a: any let b: unkonwn a.toFixed() b.toFixed()

  • void 無類型

常用于沒有具體傳回值的函數

const handler = (param: string):void => {}

  • never 類型

永遠不存在的值

任何類型的字類型, 可以指派給任何類型

但是任何類型都不可指派給 never, 包括 any

function error(msg: string): never { throw new Error('我報錯了'); // 直接異常結束了 } function loop(): never { while (true) {} } function fn(x: number | string) { if (typeof x === 'number') { // 類型保護 console.log(x); } else if (typeof x === 'string') { console.log(x); } else { console.log(x); } }

類型推論

如果沒有指定類型, TS 會根據類型推論推斷出一個類型.

如果變量定義的時候沒有指派, 預設是 any 類型

let x; // 可以指派為任何類型的值 let x1 = '生生世世'; // x1會推論成sring類型, 不能給x1指派為其他類型了 // x1 = 222;

如果 TS 能正确推斷出其類型, 我們可采用類型推論而不必定義類型

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

聯合類型

let name1: string | number; // console.log(name1.toFixed()); // console.log(name1.toString()); name1 = 3; name1 = 'sxh';

類型斷言

類型斷言用來告訴編譯器 “我知道自己在幹什麼”, 有 尖括号 和 as 兩種寫法. 在 tsx 文法中, 隻支援 as.

let name1: string = '111' let name2: string | number; // console.log(name2.toFixed()) console.log((name2 as number).toFixed()); // 雙重斷言 console.log((name1 as any) as boolean);

字元串字面量類型

字元串字面量類型用來限制取值隻能是某幾個字元串中的一個

“類型别名與字元串字面量類型都是使用 type 進行定義”

type Pos = 'left' | 'right'; function loc(pos: Pos) {} // loc('up')

字元串字面量 vs 聯合類型

type T1 = '1' | '2' | '3'; type T2 = string | number | boolean; let t1: T1 = '2'; let t2: T2 = true;

函數

函數定義

定義函數有兩種方式: 1. 函數定義 2.函數表達式

一個函數有輸入和輸出,要在 TypeScript 中對其進行限制,需要把輸入和輸出都考慮到

// 1.直接聲明 function person2(name: string): void { console.log(name); } person2('sxh'); // 2.變量聲明 let sum: (x:number, y:number) => number sum = (a,b)=>a+b sum(1,2) // 3.類型别名 type Sum = (a: number, b: number) => void; let sum: Sum = function (a: number, b: number): number { return a + b; }; let sum2: Sum = function (a: number, b: number): void {// 沒有傳回值 // return a + b; }; const myAdd = (x: number, y: number) => x + y; // 也可以直接這樣定義, 類型會自動推導 // 4.接口 interface Sum{ (x:number, y: number):number } let sum: Sum = (a,b)=>a+b sum(1,2)

“在 TypeScript 的類型定義中,=> 用來表示函數的定義,左邊是輸入類型,需要用括号括起來,右邊是輸出類型。”

可選參數

必須放在最後一個參數位置

function sum3(a: number, b: number, c?: number) {} sum3(1, 2); // 設定預設值, 最後一個參數設定預設值, 函數調用可傳可不傳, 相當于可選參數 function sum4(a: number, b: number, c = 6) { return a + b + c; } sum4(1, 2); // 第一個參數設定預設值, 使用預設調用的時候必須傳 undefiend function sum5(a = 3, b: number) { return a + b; } console.log(sum5(undefined, 2));

剩餘參數

function sum6(...args: number[]) { return args.reduce((val, item) => val + item, 0); } sum6(1, 2, 3, 5);

函數的重載

給同一個函數提供多個函數定義

let catOpt: any = {}; function cat(param: string): void; function cat(param: number): void; function cat(param: any) { if (typeof param === 'string') { catOpt.name = param; } else if (typeof param === 'number') { catOpt.age = param; } } cat('小花'); cat(3); function add(a: string, b: number): void; function add(a: number, b: number): void; function add(a: string | number, b: string | number): void {} add(1, 2); // add(1, '2');

如何定義類

class Book { name: string; getName(): void { console.log(this.name); } } let book1 = new Book(); book1.name = 'ts'; book1.getName();

存取器

通過存取器來改變一個類中屬性的讀取和指派行為

class MyBook { bname: string; // 屬性 constructor(bname: string) { this.bname = bname; } get name() { return this.bname; } set name(value) { this.bname = value; } } let myBook = new MyBook('ts'); myBook.name = 'js'; console.log(myBook.name);

參數屬性

class MyBook1 { // bname: string; constructor(public bname: string) { // this.bname = bname; } get name() { return this.bname; } set name(value) { this.bname = value; } } let myBook1 = new MyBook1('ts'); myBook1.name = 'js'; console.log(myBook1.name);

readonly

class MyBook2 { readonly bname: string; // 公開的隻讀屬性隻能在聲明時或者構造函數中指派 readonly num: number = 1; constructor(bname: string) { this.bname = name; } changeName(value) { // this.bname = value; } }

繼承

class Animal { name: string; constructor(name: string) { this.name = name; } eat(food: string) { console.log('吃什麼', food); } } class Cat extends Animal { color: string; constructor(name: string, color: string) { super(name); this.color = color; } } let cat = new Cat('哈哈', 'white'); console.log(cat.name); cat.eat('fish');

類裡面的修飾符

public 公有屬性, private私有屬性, protected受保護的

// public 公有屬性 , ts預設為public class Animal1 { public name: string; // 自己, 子類和執行個體都可以通路 private age: number = 2; // 自己可以通路, 子類 和 執行個體 都不可以通路, protected body: string; // 自己和子類可以通路, 執行個體不可以通路 public constructor(name: string, age: number) { this.name = name; this.age = age; } public eat(food: string) { console.log('吃什麼', food); } private getAge() { return this.age; } } class Dog extends Animal1 { color: string; constructor(name: string, age: number, color: string) { super(name, age); this.color = color; } dogInfo() { // console.log(this.name); // console.log(this.body); // console.log(this.age); } } let an = new Animal1('哈哈', 2); let dog = new Dog('哈哈', 2, 'white'); console.log(dog.name); // console.log(dog.age); // console.log(dog.body); // console.log(dog.getAge());

靜态屬性 靜态方法

類自身上的屬性和方法

class Button { static type = 'link'; static getType() { return Button.type; } public content: string; constructor(content: string) { this.content = content; } } console.log(Button.getType); console.log(Button.type); class SubButton extends Button {} let btn = new SubButton('ok'); SubButton.type = 'e'; console.log(Button.type); console.log(SubButton.type);

抽象類

是抽象概念, 不能被執行個體化

abstract class Input { label: string; abstract changeValue(): void; // 此方法在子類中必須得實作 } class SearchInput extends Input { changeValue() {} // 必須得實作抽象類裡抽象方法 }

抽象方法 不包含具體實作, 必須在子類中實作

有關鍵字 abstract

接口

  • 接口

1.定義對象的類型,描述對象的形狀

interface Cats { // 多屬性和少屬性都不行 name: string; text?: string; // 可選屬性 speak(): void; } let Cat: Cats = { name: 'sxh', speak() {}, };

  1. 還可以表示對行為的抽象,而具體如何行動需要由類(classes)去實作(implement

    同名接口可以寫多個, 類型會自動合并

interface Plus { add(): void; } interface Minus { minus(): void; } class Compute implements Plus, Minus { //使用接口限制類 add() {} minus() {} }

  • 任意屬性

interface Book { readonly id: number; name: string; // [key: string]: any; } let b: Book = { id: 1, name: 'sxh', // age: 18, };

  • 接口的繼承

interface Book1 { getName(): void; } interface Book2 extends Book1 { getAuth(): string; } class Ts implements Book2 { // 兩個接口裡的方法都得實作 getName() {} getAuth() { return 'sss'; } }

  • readonly

interface Book3 { readonly id: number; } let b3: Book3 = { id: 2, }; // b3.id = 8

  • 函數類型接口 函數類型接口, 接口修飾函數

interface SumHandle { (a: number, b: number): number; } const sum11: SumHandle = (a: number, b: number): number => { return 2; }; // 修飾函數,描述函數 interface Data1 { (id: number): any; // label: string; } // 描述對象的屬性name interface Data2 { name: (id: number) => any; } let e: any = () => {}; e.label = 'ts'; // let d1: Data1 = e; let d1: Data1 = () => {}; let d2: Data2 = { name() {}, };

  • 可索引接口 對數組和對象進行限制

interface Lists { [id: number]: number; } const list: Lists = [1, 3, 4]; const listObj: Lists = { 0: 2, 3: 5, };

  • 類接口

構造函數的類型

class List { constructor(public id: number) {} } // 修飾普通函數 // 如果加上 new 之後描述構造函數類型 interface Data { new (id: number): any; } // let l : Data = List // 類本身作為參數傳遞, 限制參數為構造函數類型 function createClass(constr: Data, id: number) { return new constr(id); } let list2: List = createClass(List, 66); console.log(list2.id);

  1. 構造函數類型的函數類型
  2. 類的執行個體類型

class App { static state: string = 'attr1'; state: string = 'attr2'; } // let com = App; // App類名本身表示的是執行個體的類型 // ts中有兩個概念一個是類型, 一個是值;冒号後面的是類型, 等号後面的是值 let aa: App = new App(); let bb: typeof App = App;

結構類型系統

接口的相容性

ts 類型的檢查原則, 有一個東西看起來像鴨子、聽起來像鴨子、叫起來也像鴨子,那麼我們就可以認為他是鴨子

當一個類型 Y 可以被指派給另一個類型 X 時, 就可以說類型 X 相容類型 Y

X 相容 Y: X(目标類型) = Y(源類型)

interface bb { name: string; age: number; id: string; } interface cc { name: string; age: number; } let c1: cc = { name: 'sxh', age: 18, }; let b1: bb = { name: 'kkb', age: 11, id: 'abc111', }; function getAge(c: cc): void {} getAge(c1); getAge(b1); // b1裡包含此中所有的屬性就可以

基本類型的相容性

let aa: string | number; let str: string = 'sxh'; // aa = str; // str = aa;

類的相容性

構造函數和靜态成員是不進行比較的, 隻要他們有相同的執行個體對象, 就是互相相容的, 如果兩個類中有私有成員就不互相相容了

class A { constructor(a: number, b: number) {} id: number = 1; // private name: string = 'ss'; } class B { constructor(a: number) {} id: number = 2; // private name: string = 'ssf'; } let a = new A(1, 2); let b = new B(3); a = b; b = a; class C extends A {} let c = new C(1, 3); c=a; a=c; // 父類和子類結構相同,互相相容 }

函數的相容性

1.比較參數

type Func = (a: number, b: number) => void; // 函數類型,目标類型 let sum22: Func; function fn1(a: number, b: number): void {} sum22 = fn1; // 少一個可以 function fn2(a: number): void {} sum22 = fn2; // 少兩個可以 function fn3(): void {} sum22 = fn3; // 多一個不可以 function fn4(a: number, b: number, c: number): void {} // sum22 = fn4; //報錯, sum22隻有兩個參數, 永遠不可能多一個, 多傳一個參數就永遠不會滿足條件

2.比較傳回值

type g = () => { name: string; age: number }; // 這個是類型定義,不是箭頭函數 let gg: g; function g1() { return { name: 'sxh', age: 11 }; } gg = g1; function g2() { return { name: 'sxh', age: 11, id: '333' }; } gg = g2; // 傳回值裡多一個參數可以, 其他屬性可以滿足就可以 function g3() { return { name: 'sxh' }; } // gg = g3;// 少一個不行

函數參數的雙向協變

傳回值類型是協變的,而參數類型是逆變的

傳回值類型可以傳子類,參數可以傳父類

參數逆變父類 傳回值協變子類

type Fn = (a: number | string) => number | string; function run(fn: Fn): void {} // type F = (a: string) => number; // let f: F; // run(f); // type F = (a: string) => string | number | boolean; // let f: F; // run(f); // type F = (a:number | string | boolean) => string | number | boolean; // let f: F; // run(f); type F = (a:number | string | boolean) => string ; let f: F; run(f);

Ts 中, 參數類型是雙向協變的 ,也就是說既是協變又是逆變的,而這并不安全。可以通過配置 strictFunctionTypes 參數修複這個問題

枚舉的相容性

枚舉類型與數字類型相容,并且數字類型與枚舉類型相容

不同枚舉類型之間是不相容的

//數字可以賦給枚舉 enum Colors {Red,Yellow} let c:Colors; c = Colors.Red; c = 1; c = '1'; //枚舉值可以賦給數字 let n:number; n = 1; n = Colors.Red;

類型保護

typeof 類型保護

instanceof 類型保護

null 保護

鍊判斷運算符

可辨識的聯合類型

in 操作符

自定義的類型保護

命名空間

namespace 解決一個子產品内命名沖突的問題

// 直接在檔案裡寫的話就是全局變量, 會與其他檔案相同的變量沖突 //let a = 1; // let b = a; namespace Box { // Box是全局唯一的, 裡面導出的變量不能重複 export class book1 {} }

如果檔案裡出現了import或者export, 那麼這個檔案就是外部子產品, 簡稱子產品, 裡面的變量都是私有變量

// 解決全局變量的問題 export let a = 1; export let b = 2; const c = 3; namespace Box { export class book1 {} }

項目環境安裝

一個簡單的項目初始化

npm i react react-dom @types/react @types/react-dom -S npm i webpack webpack-cli html-webpack-plugin -D npm i typescript ts-loader source-map-loader -D

建立ts配置檔案, tsconfig.json

{ "compilerOptions": { "outDir": "./dist", // 輸出目錄 "sourceMap": true, "strict": true, "noImplicitAny": true, // 是否允許出現隐含的any類型 "jsx": "react", // 如何編譯jsx文法, 生成的代碼是純的js "module": "commonjs", // 子產品規範, 要把所有的子產品編譯成什麼子產品 "target": "es5", // 編譯目标 "esModuleInterop": true // 允許在commonjs子產品和es module進行轉換 相容其他子產品的導入方式 }, "include": ["./src/**/*"] // 隻編譯src目錄下面的ts檔案 }

建立webpack.config.js檔案

const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); module.exports = { mode: "development", entry: "./src/index.tsx", output: { filename: "bundle.js", path: path.join(__dirname, "dist"), }, devtool: "source-map", devServer: { hot: true, contentBase: path.join(__dirname, "dist"), }, resolve: { extensions: [".ts", ".tsx", ".js", ".json"], alias: { "@": path.resolve("src"), }, }, module: { rules: [ { test: /.tsx?

在src目錄中建立index.tsx、index.html檔案, 編寫完元件就可以啟動項目了

index.tsx

import React, { Component } from 'react'; import ReactDom from 'react-dom'; import Count from './count1'; ReactDom.render(, document.getElementById('root'));

count1.tsx

import React, { Component } from 'react'; interface IProps { num: number; // name: string; } // 函數元件 // const Count = (props: Props) =>

{props.num}

; // Count.defaultProps = { // num: 10, // }; //類元件 interface State { count: number; } export default class Count extends Component<IProps, State> { state: State = { count: 0, }; static defaultProps = { num: 10, }; render() { return ( <>

點選了{this.state.count}次

<button onClick={() => { this.setState({ count: ++this.state.count }); }} > 點選 </> ); } }