Vue & TypeScript 初體驗 - TypeScript中的Interface

前文回顧html

在這兩篇中, 主要介紹了在vue 2.x版本中使用TypeScript面向對象編程時, 一些在編寫Vue組件時語法上的變化. 以及使用Vuex時一些變化.vue

本文主要介紹下, 利用TypeScript語言的特性, 如何有效利用Interface進行面向接口編程.git

通常在實現一個系統的時候,一般是將定義與實現合爲一體,不加分離的,我認爲最爲理想的系統設計規範應是全部的定義與實現分離,儘管這可能對系統中的某些狀況有點麻煩。github

1. 什麼是接口?

在面嚮對象語言中,接口Interfaces是一個很重要的概念,它是對行爲的抽象,而具體如何行動須要由類class去實現implementsvuex

TypeScript 中的接口是一個很是靈活的概念,除了可用於對類的一部分行爲進行抽象之外,也經常使用於對「對象的形狀(Shape)」進行描述。typescript

引伸思考: 面向對象/面向過程/面向接口編程編程

  • 面向過程是指,咱們考慮問題時,以一個具體的流程(事務過程)爲單位,考慮它的實現
  • 面向對象是指,咱們考慮問題時,以對象爲單位,考慮它的屬性及方法
  • 面向接口編程,原意是指面向抽象協議編程,實現者在實現時要嚴格按協議來辦。

2. 什麼要使用接口?

舉例來講明一下.數據結構

假設一個場景, 咱們須要根據userInfo對象, 獲取用戶的userId, 代碼可能以下:dom

// utils/env.js
function getUserId(userInfo) {
    return userInfo.account.id
}
複製代碼

在JavaScript諸如此類的函數可能會不少, 有可能會引起如下問題:函數

  • 傳入的實參數據結構不對, 要能會獲得''空值.
  • 在迭代開發中, 有人誤修改了形參的數據結構, 致使取不到指望的值, 緣由排查困難(公用函數會在多處使用)

JavaScript是弱類型語言, 並不會對傳入的參數作過多的檢測, 須要咱們本身添加相關判斷. 以上示例代碼可考慮修改成:

// utils/env.js
function getUserId(userInfo) {
    if(userInfo && Object.prototype.call(userInfo.account).slice(8, -1) === 'Object') {
        return userInfo.account.id
    }
    window.console.warn("Can't get user accout id, pls check!")
    return ''
}
複製代碼

相信工程當中不少核心的業務數據, 是須要強校驗的.

但若是使用TypeScript中的Interface, 就簡單多了.

使用TypeScript Interface實現

// utils/env.ts
interface Account {
  id: string;
  name: string;
}

interface UserInfo {
  account: Account;
  loginDate: string;
}

export function getUserId(userInfo:UserInfo):string {
  return UserInfo.account.id
}
複製代碼

3. TypeScript Interface

1). Interface 基礎

TypeScript中定義接口使用interface關鍵字來定義.

以下簡單示例:

// 定義接口
interface Account {
  id: string;
  name: string;
}
export default interface Person{
  id: number, // 必選屬性
  name: string, // 必選屬性
  age: number, // 必選屬性
  job?: string, // 可選屬性,表示不是必須的參數,
  readonly salary: number, // 只讀屬性, 表示是隻讀的屬性,可是在初始化以後不能從新賦值,不然會報錯
  [ propName : string ] : any, // 任意屬性, 全部屬性的屬性名必須爲string類型, 值能夠是 任意類型
  getUserId(account:Accout): string; // 定義方法
}
// 定義一個變量,它的類型爲接口Person,這樣便可約束接口的內容了.
let person: Person = {
  name: 'james',
  age: 30,
  job: 'IT dog',
  id: 9527,
  salary: 1000000000,
  // 注意, hello, demo是顯式定義的屬性, 適用於[propName:string]: any規則, 所以值類型爲: any
  hello: 'world',
  demo: [1, 3, 'world'],
  getUserId(account:Account):string{return account.id}
}

function printMan (person:Person) {
  window.console.log(`My name is: ${person.name}, my job is: ${person.job}`)
}
複製代碼

此例是一個簡單的接口實例, 利用接口來約束傳入變量的內容. 須要注意的是, 在賦值時, 變量的shape必須與接口shape保持一致.

接口類型的幾類屬性

  • 必選屬性, ":" 帶冒號的屬性是必須的,不可少
  • 可選屬性, "?" 帶問號的屬性, 表示是可選的
  • 只讀屬性, "readonly", 有時候咱們但願對象中的一些字段只能在建立的時候被賦值,那麼能夠用 readonly 定義只讀屬性. ,只讀的約束存在於第一次給對象賦值的時候,而不是第一次給只讀屬性賦值的時候:
  • 任意屬性 [ propName : 類型 ] : any, 表示定義了任意屬性取string 類型的值 須要注意的是,一旦定義了任意屬性,那麼肯定必選和可選屬性都必須是它的子屬性

2). Interface 進階

函數類型接口

Interface 能夠用來規範函數的入參和出參。
例如:

interface Account{
    id: string;
    firstName: string;
    lastName: string;
    getUserFullName(firstName: string, lastName: string): string
}
複製代碼

以上接口中定義了一個getUserName函數, 用於獲取用戶的全名.

接口的實現

// 定義接口
interface Account{
  id: string;
  firstName: string;
  lastName: string;
  getUserFullName(firstName: string, lastName: string): string
}
// 實現接口
export default class Person implements Account {
  id: string;
  firstName: string;
  lastName: string;

  constructor(id: string, firstName: string, lastName: string) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName
  }
  getUserFullName (firstName: string, lastName: string) : string {
    return firstName + " " + lastName
  }
}
複製代碼

初學者可能會有疑問, 爲什麼Interface須要預先定義, 然後又去實現它, 爲什麼不能夠直接實現呢?

其實主要有兩方面的考慮:

  • 規範, 接口實際上是一種規範, 一般用來:
    • 定義規範(或數據結構), 必須遵照該規範
  • 程序設計, 接口中定義了類必須實現的方法和屬性, 以達到規範和具體實現分離, 加強系統的可拓展性和可維護性.

接口繼承

單接口繼承
interface Account{
  id: string;
  firstName: string;
  lastName: string;
  getUserFullName(firstName: string, lastName: string): string
}

// 接口繼承使用`extends`
interface UserInfo extends Account {
  nickName: string;
}
複製代碼
多接口繼承

一個接口能夠同時繼承多個interface, 實現多個接口成員的合併. 例如:

interface Email {
  domain: string;
  address: string;
}
interface Account{
  id: string;
  firstName: string;
  lastName: string;
  getUserFullName(firstName: string, lastName: string): string
}

// 多接口繼承
interface UserInfo extends Account, Email {
  nickName: string;
}
複製代碼

須要注意的是, 在繼承多個接口時

  • 定義的同名屬性的類型不一樣的話,是不能編譯經過的。
  • 在實現(implements)接口時, 須要實現全部繼承接口中的屬性和方法

例如, 若要實現接口UserInfo, 那麼其繼承的接口Account, Email中全部屬性都要實現. 示例代碼以下:

interface Email {
  domain: string;
  address: string;
}
interface Account{
  id: string;
  firstName: string;
  lastName: string;
  getUserFullName(firstName: string, lastName: string): string
}

interface UserInfo extends Account, Email {
  nickName: string;
}

class Person implements UserInfo{
  id: string;
  firstName: string;
  lastName: string;
  domain: string;
  address: string;
  nickName: string;
  constructor(id: string, firstName: string, lastName: string, domain: string, address: string): void{
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.nickName = this.firstName;
    this.domain = domain;
    this.address = address;
  }

  getUserFullName (firstName: string, lastName: string) : string {
    return firstName + " - " + lastName
  }
}
複製代碼

3). 可索引類型的接口

interface StrArray {
  readonly [index: number]: string // 注意: index 只能爲 number 類型或 string 類型
  length: number // 指定length屬性
}

let strArr:StrArray = ['hello', 'james']
strArr[1] = 'demo' // 經過索引賦值是不被容許的, 由於設置了index爲readonly屬性
複製代碼

4). 類Interface

Interface 也能夠用來定義一個類的形狀。須要注意的是類 Interface 只會檢查實例的屬性,靜態屬性是須要額外定義一個 Interface;好比:

// Person.ts
// PersonConstructor => 用以檢查靜態屬性或方法
interface PersonConstructor {
  new (name: string, age: number): void
  typename: string // 靜態屬性
  getType(): string // 靜態方法
}

interface PersonInterface {
  log(msg: string): void
}

// 不可寫成: class Person implements PersonInterface, PersonInterface
const Person: PersonConstructor = class Person implements PersonInterface {
  name: string
  age: number
  static typename = 'Person type'
  static getType(): string {
    return this.typename
  }

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  log(msg: string): void {
    window.console.log(msg)
  }
}

export default Person

複製代碼

須要注意的是: 靜態屬性和方法的檢查, 與實例屬性和方法的檢查應使用不一樣的interface.

5). 繼承類的Interface

Interface 不只可以繼承 Interface 還可以繼承類,再建立子類的過程當中知足接口的描述就會必然知足接口繼承的類的描述

// ExtendsDemo.ts
class Animal {
  // 類的靜態屬性
  static clsName: string
}

interface Dog extends Animal {
  wangwang(): void
}

// 第一種方法, 實現Dog接口
class WangCai1 implements Dog {
  // Dog接口繼承至Animal類, 所以規範了 clsName 屬性
  static clsName: '旺財'
  // Dog接口有對wowo方法進行描述
  wangwang() {
    window.console.log('旺旺旺...')
  }
}

// 第二種方法, 繼承Animal類, 實現Dog接口
class WangCai2 extends Animal implements Dog {
  static clsName: 'Wang Cai'
  wangwang() {
    window.console.log('旺旺旺...')
  }
}

複製代碼

4. type & interface的異同

1). 相同點

均可以用來描述一個函數或對象, 如:

// TypeAndInterface.ts
interface Person{
  name: string;
  age: number;
  getName():string;
}
type Person1 = {
  name: string;
  age: number;
  getName(): string;
}
複製代碼

均可以使用extends繼承

// TypeAndInterface.ts
interface Person {
  name: string
  age: number
  getName(): string
}
type Person1 = {
  name: string
  age: number
  getName(): string
}

// type繼承type聲明的接口
type Person2 = {
  firstName: string
  lastName: string
}
type User = Person2 & { age: number }

// interface繼承type聲明的接口
interface User1 extends Person2 {
  age: number
}

// type繼承interface聲明的接口
type User2 = User1 & { getFullName(): void }
複製代碼

2). 不一樣點

type能夠聲明特定類型, 如:

  • 基本數據類型的別名
  • 聯合類型
  • 元組等類型
  • 可使用typeof獲取實例的類型並進行賦值
// TypeAndInterface.ts

/** type能夠聲明基本類型別名, 聯合類型, 元組等類型, 也能夠經過使用typeof獲取實例的類型進行賦值 */
// 基本數據類型的別名
type Str = string

// 聯合類型
type StrOrNumber = string | number
type Message = string | { text: string }
type Tree<T> = T | {left: Tree<T>, right: Tree<T>}

// 元組
// 具體指定UserArr的每一個位置的數據類型
type UserArr = [string, number]
let demo: UserArr = ['hello', 30] // 只能夠被依次賦值爲: string, number, 不然會報錯

type Arr<T> = [T, T];
type Coords = Arr<number>

// 使用typeof獲取實例的類型, 並進行賦值
const img = window.document.createElement('img')
type ImgElement = typeof img
複製代碼

interface能夠聲明合併

// TypeAndInterface.ts

// interfact能夠聲明合併
interface Man {
  name: string
  age: number
}

interface Man {
  sex: string
}

/** * 等價於: interface Man{ name: string; age: number; sex: string; } */
複製代碼

注意: 在項目中並不建議你們這麼使用.

什麼時候使用type/interface

在不肯定使用type/interface時, 請優先考慮使用interface, 若interface沒法知足需求時, 才考慮使用type.

相關連接

相關文章
相關標籤/搜索