前文回顾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.