走近Ts,用了爽,用後一直爽(一)

yaoxfly發表於2020-12-19

前言

vue3已經發布了,ts的腳步已經阻攔不住了,還只會es6?別想了,人家都已經在行動了,以下是ts的基本系列教程,ts的基本語法,高階語法等,以及在vue專案中如何應用ts,跟著我趕緊擼起來吧。

基本資料型別

數字
const a: number = 3;
字串
const b: string = "1";
陣列
const c: number[] = [1, 2, 3];
const d: Array<number> = [1, 3];
const arr: any[] = [1, "33", true];
元組

可以為陣列中的每個引數定義相對應的型別

const e: [number, string] = [1, "ww"];
列舉
enum error {
  blue = 3,
  "orange",
}
const f: error = error.orange;
console.log(f); //輸出4

tips

  1. 如果未賦值的上一個值是數字那麼這個未賦值的值的是上一個值的值+1
  2. 如果未賦值上一個值未賦值那麼輸出的就是它的下標
  3. 如果未賦值的上一個值的值是非數字,那麼必須賦值
布林型別
const g: boolean = true;
物件

const i: object = {};
undefined

常用於組合型別

let j: number | undefined;
null
let k: null;
void

指定方法型別,表示沒有返回值,方法體中不能return

function aa(): void {
  console.log(1);
}

//如果方法有返回值,可以加上返回值的型別
function bb(): number {
  return 1;
}
never

其他型別 (包括null和undefined)的子型別,代表從不會出現的值

let l: never;

//匿名函式並丟擲異常
l = (() => {
  throw new Error("111");
})();
任意型別

讓引數可以是任何一種型別


let h: any = 1;
h = true;
h = "st";

函式

函式申明
function cc(): void {}
方法傳參
function getUserInfo(name: string, age?: number, school: string = "清華大學") {
  return `name:${name}--age:${age}--school:${school}`;
}
tips: ?代表這個引數可傳可不傳,不傳就是undefined,也可定義個預設的值
剩餘引數

傳遞多個時,如果用了剩餘引數,就可以把未定義的形參轉換為陣列。

function sum (a: number, b: number, ...arr: number[]): number {
  let sum: number = a + b;
  arr.forEach((element) => {
    sum += element;
  });
  console.log(arr); [3,4,5]  
  return sum;
}
console.log(sum(1, 2, 3, 4, 5)); //15
函式過載
function reload(name: string): string;
function reload(age: number): string;
function reload(param: any): any {
  return typeof param === "string" ? `我是:${param}` : `我的年齡:${param}`;
}
console.log(reload(18)); //年齡
tips: 被過載的方法,是沒有方法體,可以根據引數的型別走其中一個方法並判斷引數,但如果傳入的引數型別不是任何被過載方法的引數型別就不允許通過。
 第 1 個過載(共 2 個),“(name: string): string”,出現以下錯誤。
   型別“never[]”的引數不能賦給型別“string”的引數。
 第 2 個過載(共 2 個),“(age: number): string”,出現以下錯誤。
   型別“never[]”的引數不能賦給型別“number”的引數


class Person {
  // 私有變數
  private name: string;
  
  // 建構函式
  constructor(name: string) {
    this.name = name;
  }
  
  // 獲取名字
  getName(): string {
    return this.name;
  }
  
  // 設定名字
  setName(name: string): void  {
    this.name = name;
  }
}

let p = new Person("張三");
p.setName("李四");
console.log(p);

繼承

class Son extends Person {
 // 靜態屬性
  public static age: number = 18;
  // 學校
  public school: string;
  //構造方法
  constructor(name: string, school: string) {
    // 訪問派生類的建構函式中的 "this" 前,必須呼叫 "super",初始化父類建構函式 --並把引數傳給父類
    super(name); 
    //把傳進來的school賦值給全域性變數
    this.school = school;
  }
  //靜態方法
  static run(name: string): string {
    return `${name}在跑步,他的年齡才${this.age}`;
  }
}

let son = new Son("王五", "清華大學");
son.setName("趙六"); // 私有類也不能在子類的外部訪問,但可通過公開的方法中進行賦值和訪問
console.log(son);
console.log(Son.run("方七"));
console.log(Son.age);

tips:

  1. public 在當前類裡面,子類,類外面都可以訪問
  2. protected 在當前類和子類內部可以訪問,類外部無法訪問
  3. private 在當前類內部可訪問,子類,類外部都無法訪問。
  4. 屬性不加修飾符,預設就是公有的 (public)

多型

通過抽象方法/方法過載--實現多型--多型的作用是用來定義標準

// 抽象父類
abstract class Animal {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
  //抽象成員--方法
  abstract eat(): any;
  //抽象成員--屬性
  protected abstract ages: Number;
  sleep(): void {
    console.log("睡覺");
  }
}

class cat extends Animal {
  ages: Number = 2;
  constructor(name: string) {
    super(name);
  }
  //非抽象類“cat”不會自動實現繼承自“Animal”類的抽象成員“eat”,  必須手動定義父類中的抽象方法--多型
  eat(): string {
    return "貓吃魚";
  }

  //多型
  sleep(): string {
    return "貓在睡覺";
  }
}

console.log(new cat("33").sleep());

tips:

  1. 抽象類無法例項化
  2. 非抽象類繼承抽象父類時不會自動實現來自父類的抽象成員,必須手動定義父類中的抽象成員,否則報錯。
  3. 抽象成員包括屬性方法

介面

  在物件導向的程式設計中,介面是一種規範的定義,它定義了行為和動作的規範,

  在程式設計裡面,介面起到一種限制和規範的作用。

  介面定義了某一批類所需要遵守的規範,介面不關心這些類的內部狀態資料,也不關心這些類裡方法的實現細節,它只規定這批類裡必須提供某些方法,提供這些方法的類就可以滿足實際需要。ts中的介面類似於java,同時還增加了更靈活的介面型別,包括屬性、函式、可索引和類等。

屬性介面
interface InterfaceName {
  first: string;
  second?: string; //加個問號,介面屬性就可以變成可傳可不傳了,不傳預設是undefined。
}
//列印變數
function logParam(name: InterfaceName): void {
  console.log(name.first, name.second, 11);
}
//定義引數
const obj = { first: "1", second: "fff", three: 1 };
//logParam({ first: "1", second: "1", three: 1 }); //報錯,只能傳介面定義的值
logParam(obj);
tips: 用個變數來儲存傳入的變數,這樣可以傳入定義的介面以外的值,否則如果直接傳入物件中無介面定義的值會報錯,所以建議介面定義了哪些值就傳哪些值。
函式型別介面

對方法傳入的引數型別,以及返回值型別進行約束,可批量進行約束。

interface keyMap {
  (key: string, value: string): string;
}
let logKeyMap: keyMap = function (key1: string, value: string): string {
  return key1 + value;
};
console.log(logKeyMap("key1", "value"));
tips: 介面只對傳入的引數的型別和引數的個數進行約束,不對引數名稱進行約束。
可索引介面
  • 約束陣列
interface Arr {
  [index: number]: string;
}
let ss: Arr = ["2121"];
  • 約束物件
interface Obj {
  [index: string]: string;
}

let interfaceArr: Obj = { aa: "1" };

tips:

  1. 陣列進行約束,index後必須跟著number型別。
  2. 物件進行約束,index後必須跟著string型別
  3. 索引簽名引數型別必須為 "string" 或 "number"
類型別介面
  • 進行約束,類似抽象類的實現。
interface Animals {
  name: string;
  eat(): void;
}

class Dogs implements Animals {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  eat() {}
}
  • 介面繼承--介面可以繼承介面
interface Dog {
  eat(): void;
}

interface Persons extends Dog {
  work(): void;
}

class Cat {
  code() {
    console.log("貓在敲程式碼");
  }
}

//可繼承類後再實現介面
class SuperMan extends Cat implements Persons {
  eat(): void {
    console.log(1);
  }
  work(): void {
    console.log(2);
  }
}
let superMan = new SuperMan();
superMan.code();
tips: 類介面會對類的屬性方法進行約束,類似非抽象類繼承抽象類時必須實現某些方法和屬性,但對屬性和方法的型別的約束更加嚴格,除了方法void型別可被重新定義外,其他屬性或方法的型別定義需要和介面保持一致。

泛型

  軟體工程中,我們不僅要建立一致的定義良好的api,同時也要考慮可重用性。
元件不僅能夠支援當前的資料型別,同時也能支援未來的資料型別,這在建立大型系統時為你提供了十分靈活的功能

   泛型就是解決介面方法複用性,以及對不特定資料型別的支援。

  要求:傳入的引數和返回的引數一致

函式的泛型
function getDate<T>(value: T): T {
  return value;
}
console.log(getDate<number>(123));
tips: 這裡的T可改成其他任意值但定義的值,和傳入的引數以及返回的引數是一樣的,一般預設寫法是T,也是業內規範的選擇。
類的泛型
class MinClass<T> {
  public list: T[] = [];
  //新增
  add(value: T): void {
    this.list.push(value);
  }
  
  //求最小值
  min(): T {
    //假設這個值最小
    let minNum = this.list[0];
    for (let i = 0; i < this.list.length; i++) {
    //比較並獲取最小值
    minNum = minNum < this.list[i] ? minNum : this.list[i];
    }
    return minNum;
  }
}
//例項化類 並且指定了類的T的型別是number
let minClass = new MinClass<number>(); 
minClass.add(23);
minClass.add(5);
minClass.add(2);
console.log(minClass.min());
 //例項化類 並且指定了類的T的型別是string,則其方法的傳參和返回都是string型別
let minClass2 = new MinClass<string>();
minClass2.add("23");
minClass2.add("5");
minClass2.add("2");
console.log(minClass2.min());
介面的泛型
  • 第一種寫法
interface ConfigFn {
  //規範引數型別,返回值型別
  <T>(value: T): T;
}

let getData: ConfigFn = function <T>(value: T): T {
  return value;
};

console.log(getData<string>("z11"));
  • 第二種寫法

interface ConfigFn<T> {
  //引數型別 ,返回值型別
  (value: T): T;
}

//介面方法
function getData<T>(value: T): T {
  return value;
}

//使用介面
let myGetDate: ConfigFn<string> = getData;
console.log(myGetDate("3"));
tips:介面的泛型只針對函式型別的介面
類當做引數傳入泛型類
//使用者類--和資料庫表欄位進行對映
class User {
  username: string | undefined;
  password: string | undefined;
  //建構函式-初始化引數
  constructor(param: {
    username: string | undefined;
    password?: string | undefined;
  }) {
    this.username = param.username;
    this.password = param.password;
  }
}


//資料庫類
class Db<T> {
  add(user: T): boolean {
    console.log(user);
    return true;
  }
  updated(user: T, id: number): boolean {
    console.log(user, id);
    return true;
  }
}

let u = new User({
  username: "張三",
});

//u.username = "李四";
u.password = "111111";
let db = new Db<User>();
db.add(u);
db.updated(u, 1);
tips: 類的引數名和型別都做了約束。

模組

  內部模組稱為名稱空間,外部模組簡稱為模組,模組在其自身的作用域裡執行,而不是在全域性作用域裡;

  這意味著定義在一個模組裡的變數、函式、類等等在模組外部是不可見的,除非你明確的使用export形式之一匯出它們。

  相反,如果想使用其它模組匯出的變數,函式,類,介面等的時候,你必須要導人它們,可以使用import形式之一。

  我們可以一些公共的功能單獨抽離成一個檔案作為一個模組。
模組裡面的變數、函式、類等預設是私有的,如果我們要在外部訪問模組裡面的資料(變數、函式、類)
我們需要通過export暴露模組裡面的資料(變數、函式、類...)。
暴露後我們通過import引入模組就可以使用模組裡面暴露的資料(變數、函式、類...)

//modules/db.ts
function getDate(): any[] {
  console.log("獲取資料");
  return [
    {
      userName: "張三",
    },

    {
      userName: "李四",
    },
  ];
}

//一個模組裡面可以用多次
// export { getDate };
//一個模組裡面只能用一次
export default getDate;
 import { getDate as getDbDate } from "./modules/db";
 import getDbDate from "./modules/db";
 getDbDate();
tips: 這個除錯時瀏覽器中不能直接使用,可在nodeweakpack的環境中除錯。

名稱空間

  在程式碼量較大的情況下,為了避免各種變數命名相沖突,可將相似功能的函式、類、介面等放置到名稱空間內
TypeScript的名稱空間可以將程式碼包裹起來,只對外暴露需要在外部訪問的物件。

   名稱空間和模組的區別

  • 名稱空間:內部模組,主要用於組織程式碼,避免命名衝突。
  • 模組:ts外部模組的簡稱,側重程式碼的複用,一個模組裡可能會有多個名稱空間。
 // modules/Animal.ts
export namespace A {
  interface Animal {
    name: String;
    eat(): void;
  }

  export class Dog implements Animal {
    name: String;
    constructor(theName: string) {
      this.name = theName;
    }
    eat() {
      console.log("我是" + this.name);
    }
  }
}

export namespace B {
  interface Animal {
    name: String;
    eat(): void;
  }

  export class Dog implements Animal {
    name: String;
    constructor(theName: string) {
      this.name = theName;
    }
    eat() {}
  }
}
 import { A, B } from "./modules/Animal";
 let ee = new A.Dog("小貝");
 ee.eat();

裝飾器

  • 類裝飾器:類裝飾器在類申明之前被申明(緊靠著類申明),類裝飾器應用於類建構函式,可以用於監視,修改或者替換類定義。
function logClass(params: any) {
  console.log(params);
  //params 就是指代當前類--HttpClient
  params.prototype.apiUrl = "動態擴充套件屬性";
  params.prototype.run = function () {
    console.log("動態擴充套件方法");
  };
  params.prototype.getDate = function () {
    console.log("動態擴充套件方法2");
  };
}

@logClass
class HttpClient {
  constructor() {}
  getDate() {
    console.log(1);
  }
}

let http: any = new HttpClient();
console.log(http.apiUrl);
http.run();
http.getDate();
tips: 裝飾器會覆蓋被裝飾的類中的方法。
  • 裝飾器工廠

可傳參的裝飾器


function logClassB(param: string) {
  return function (target: any) {
    console.log(target, "裝飾器以下的類");
    console.log(param, "裝飾器傳進來的屬性");
  };
}

@logClassB("小慧")
class HttpClients {
  constructor() {}
  getDate() {}
}

let https: any = new HttpClients();
console.log(https);
  • 建構函式裝飾器
function logClassC(target: any) {
  console.log(target, 1111);
  //用在這裡繼承目標類並過載方法和屬性
  return class extends target {
    a: any = "我是修改後的屬性";
    getDate() {
      console.log(this.a + "--裝飾器中的方法輸出的");
    }
  };
}

@logClassC
class HttpClient2 {
  public a: string | undefined;
  constructor() {
    this.a = "我是建構函式裡面的a";
  }
  getDate() {
    console.log(this.a);
  }
}
const https2 = new HttpClient2();
https2.getDate();
未完待續~

下期預告:在vue中使用ts。

相關文章