前文回顧html
在這兩篇中, 主要介紹了在vue 2.x版本中使用TypeScript面向對象編程時, 一些在編寫Vue組件時語法上的變化. 以及使用Vuex時一些變化.vue
本文主要介紹下, 利用TypeScript語言的特性, 如何有效利用Interface進行面向接口編程.git
通常在實現一個系統的時候,一般是將定義與實現合爲一體,不加分離的,我認爲最爲理想的系統設計規範應是全部的定義與實現分離,儘管這可能對系統中的某些狀況有點麻煩。github
在面嚮對象語言中,接口Interfaces
是一個很重要的概念,它是對行爲的抽象,而具體如何行動須要由類class
去實現implements
。vuex
TypeScript 中的接口是一個很是靈活的概念,除了可用於對類的一部分行爲進行抽象之外,也經常使用於對「對象的形狀(Shape)」進行描述。typescript
引伸思考: 面向對象/面向過程/面向接口編程編程
- 面向過程是指,咱們考慮問題時,以一個具體的流程(事務過程)爲單位,考慮它的實現
- 面向對象是指,咱們考慮問題時,以對象爲單位,考慮它的屬性及方法
- 面向接口編程,原意是指面向抽象協議編程,實現者在實現時要嚴格按協議來辦。
舉例來講明一下.數據結構
假設一個場景, 咱們須要根據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, 就簡單多了.
// 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
}
複製代碼
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保持一致.
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
}
}
複製代碼
interface StrArray {
readonly [index: number]: string // 注意: index 只能爲 number 類型或 string 類型
length: number // 指定length屬性
}
let strArr:StrArray = ['hello', 'james']
strArr[1] = 'demo' // 經過索引賦值是不被容許的, 由於設置了index爲readonly屬性
複製代碼
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.
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('旺旺旺...')
}
}
複製代碼
// TypeAndInterface.ts
interface Person{
name: string;
age: number;
getName():string;
}
type Person1 = {
name: string;
age: number;
getName(): string;
}
複製代碼
// 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 }
複製代碼
// 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
複製代碼
// TypeAndInterface.ts
// interfact能夠聲明合併
interface Man {
name: string
age: number
}
interface Man {
sex: string
}
/** * 等價於: interface Man{ name: string; age: number; sex: string; } */
複製代碼
注意: 在項目中並不建議你們這麼使用.
在不肯定使用type/interface時, 請優先考慮使用interface, 若interface沒法知足需求時, 才考慮使用type.