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() {}, };
-
還可以表示對行為的抽象,而具體如何行動需要由類(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);
- 構造函數類型的函數類型
- 類的執行個體類型
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 }); }} > 點選 </> ); } }