理解和使用 TypeScript 中的接口

TypeScript 席卷了 JavaScript 世界,通过添加强大的打字功能让团队更聪明地工作。 这些打字功能使我们的代码更具描述性,并使我们能够更早、更轻松地识别和修复错误。

在本文中,我们将介绍 TypeScript 中最重要的打字功能之一:接口。 它们允许我们描述将在我们的代码中使用的对象,最终帮助我们捕捉错误并编写更高效的代码。

接口介绍

JavaScript 为开发人员提供了极大的灵活性。 初始化为整数的变量可以在运行时分配一个函数字面量。 JavaScript 中的变量类型是不可预测的。 正如您在下面的示例中看到的, a初始化为整数,然后分配一个函数字面量:

var a = 2
a = function () {
  console.log("I was initialized with a value of an integer, but now I'm a function")
}

让我们考虑一下建造特斯拉 Model S 汽车的实施计划。

十名特斯拉工程师构建了他们的原型模型。 在实施之前没有制定规范,所以工程师们都提出了自己的一套规范和实施模型。 其中一个原型能够向用户展示汽车充电细节,而另一个原型则配备了轮胎监控系统。

如果事先定义了一组规范,这些工程师根据规范实现原型就很方便了。 我们在用 JavaScript 构建复杂实体时处理同样的问题:

function buildTeslaModelS (teslaObj) {
    // Implementation Details
}
​
buildTeslaModelS({
    length: 196,
    width: 86,
    measureTirePressure: function () {
    },
    wheelbase: 116
})
​
buildTeslaModelS({
    length: "196",
    width: "86",
    wheelbase: "116",
    measureRemCharging: function () {
    }
})

这 buildTeslaModelS函数使用定义的参数返回一辆特斯拉 Model S 汽车 teslaObj. 它对输入参数做出一些假设,并基于这些假设返回一个模型。 它假设 length, width, 和 wheelbase属性将是整数,并且它基于此假设执行一些计算。

但是,正如您在第二个函数调用中看到的 buildTeslaModelS,这些值是字符串,因此假设不再有效。

此外,该 buildTeslaModelS函数不知道它必须处理 measureRemCharging属性,所以它完全跳过了那部分。 它假设 measureTirePressure是一个强制性属性,它应该存在于所有这些模型中。 然而,当它在第二个函数调用中没有找到这个属性时,它会在运行时抛出一个错误。

这是一个非常灵活的功能! 应该有办法告诉 buildTeslaModelS函数输入的形状 teslaObj范围。 如果有一个验证检查来检查强制属性及其类型会更容易 teslaObj在编译时。

TypeScript 接口来帮忙了!

TypeScript 内置了对接口的支持 。 接口定义实体的规范。 它列出了说明需要做什么但没有具体说明如何完成的合同。

在上面的例子中,我们可以为特斯拉 Model S 定义一个接口,然后它的每个原型都会使用这个接口来为接口中定义的各种功能提出一个实现计划。

这是特斯拉 Model S 的界面:

interface TeslaModelS {
    length: number;
    width: number;
    wheelbase: number;
    seatingCapacity: number;
    getTyrePressure: () => number;
    getRemCharging: () => number;
}

接口包含所有属性的名称及其类型。 它还包括函数的签名以及参数类型和返回类型。 例如, getTyrePressure和 getRemCharging函数返回类型的值 number.

How to use an interface

A class or function can implement an interface to define the implementation of the properties as defined in that interface.


Over 200k developers use LogRocket to create better digital experiencesLearn more →


Let’s write a function to implement TeslaModelS interface:

function buildTeslaModelS (teslaObj: TeslaModelS) {
}
​
buildTeslaModelS({
    length: "196",
    width: "86",
    wheelbase: "116",
    measureRemCharging: function () {
    }
})

当您运行上面显示的代码时,TypeScript 编译器将给出以下错误:

类型参数 { length: string; width: string; wheelbase: string; measureRemCharging: () => void; }不可分配给类型参数 TeslaModelS. 对象字面量只能指定已知属性,并且 measureRemCharging类型中不存在 TeslaModelS.

编译器抱怨有两个原因:

  1. 属性 length, width, 和 wheelbase被定义为类型 number在界面中,所以它期望它们是类型 number并不是 string

  2. 该物业 measureRemCharging接口上没有定义。 它应该被命名为 getRemCharging它应该返回一个 integer. 实体的实现应遵循其接口中定义的合同

要构建接口中定义的 Tesla Model S,我们必须像这样定义函数:

function buildTeslaModelS (teslaObj: TeslaModelS) {
}
​
buildTeslaModelS({
    length: 196,
    width: 86,
    wheelbase: 116,
    seatingCapacity: 4,
    getTyrePressure: function () {
        let tyrePressure = 20 // Evaluated after doing a few complex computations!
        return tyrePressure
    },
    getRemCharging: function () {
        let remCharging = 20 // Evaluated after doing a few complex computations!
        return remCharging
    }
})

上面的实现 teslaObj正是界面所期望的!

如何在接口中定义可选属性

接口在确保实体按预期实现方面做得很好。 但是,可能存在不需要具有接口中定义的所有属性的情况。 这些称为可选属性,并在界面中表示如下:

interface TeslaModelS {
    length: number;
    width: number;
    wheelbase: number;
    seatingCapacity: number;
    getTyrePressure?: () => number;
    getRemCharging: () => number;
}

请注意 ?在里面 getTyrePressure财产。 问号表示该属性 getTyrePressure是可选的,对于实体在所有模型中实现此功能不是强制性的。 即使您没有在 teslaObj范围。

编译器还会检查接口中未定义的多余属性。 比方说, teslaObj包含多余的属性 turningCircle, 中未指定 TeslaModelS界面:

buildTeslaModelS({
    length: 196,
    width: 86,
    wheelbase: 116,
    getTyrePressure: function () {
        let tyrePressure = 20 // Evaluated after doing a few complex computations!
        return tyrePressure
    },
    getRemCharging: function () {
        let remCharging = 20 // Evaluated after doing a few complex computations!
        return remCharging
    },
    turningCircle: 10
})

编译器给出以下错误:

类型参数 { length: number; width: number; wheelbase: number; getTyrePressure: () => number; getRemCharging: () => number; turningCircle: number; }不可分配给类型参数 TeslaModelS. 对象字面量只能指定已知属性,并且 turningCircle类型中不存在 TeslaModelS.

接口中的只读属性

只读属性一旦初始化就不能更改。 例如,属性 length, width, wheelbase, 和 seatingCapacity在用某个固定值初始化它们之后,在任何情况下都不应该修改它们。

我们将不得不修改我们的界面以反映这种变化:

interface TeslaModelS {
    readonly length: number;
    readonly width: number;
    readonly wheelbase: number;
    readonly seatingCapacity: number;
    getTyrePressure?: () => number;
    getRemCharging: () => number;
}

注意使用 readonly带有属性名称的关键字。 这表明这些属性在使用某个值初始化后无法修改。

接口中的可索引属性

顾名思义,可索引属性用于定义索引为唯一数字或字符串的类型。 例如,我们可以定义一个类型 CustomArray作为:

interface CustomArray {
    [index: number]: string
}
​
​
let cars: CustomArray = ['Hatchback', 'Sedan', 'Land Rover', 'Tesla Model S']
console.log('Element at position 1', cars[1]) // 'Sedan'

请注意 cars变量不是一个普通的数组,所以你不能使用数组内置函数,比如 push, pop, filter等。您可能会争辩说,最好定义普通数组而不是使用可索引类型。 当您必须定义应该对同一数据类型的一系列值进行操作的自定义属性和函数时,可索引类型很有帮助。

由于我们已经将特斯拉 Model S 的规格信息清晰的放在了一个界面中,提高了特斯拉工程师的工作效率,他们现在已经准备好了第一批 100 辆汽车。 现在是审查委员会检查每个模型并测试它们的性能和其他因素的时候了:

interface TeslaModelSMap {
    engineer: string,
    model: TeslaModelS,
    readonly rating: number
}
interface TeslaModelSReview {
    [id: number]: TeslaModelSMap
}
​
​
const TeslaModelSReviewQueue: TeslaModelSReview = [
    {
        engineer: 'John',
        model: modelByJohn1, // modelByJohn1 is of type `TeslaModelS`
        rating: 2
    },
    {
        engineer: 'Ray',
        model: modelByRay1, // modelByRay1 is of type `TeslaModelS`
        rating: 3
    },
    {
        engineer: 'John',
        model: modelByJohn2, // modelByJohn2 is of type `TeslaModelS`
        rating: 4
    },
    // ... other 97 models
]

这 TeslaModelSReview接口索引一组属性—— engineer, model, 和 rating与特定模型关联到唯一的数字索引。 这 TeslaModelSReviewQueue是类型 TeslaModelSReview. 它列出了由不同工程师建造的特斯拉模型。

从上面的代码中,我们可以看到 John 构建了两个模型: modelByJohn1和 modelByJohn2, 被评为 2和 4, 分别。

索引器的类型可以是字符串或数字。 我们还可以定义其他属性 TeslaModelSReview接口,但这些属性应该返回一个子类型 TeslaModelS类型。

的指数 TeslaModelSReview可以将其设为只读,以防止在审查过程中修改其值。 我们将不得不改变我们的 TeslaModelSReview像这样的界面:

interface TeslaModelSReview {
    readonly [id: number]: TeslaModelS
}

如何在接口中定义函数类型

接口也可用于定义函数的结构。 正如我们之前看到的,函数 getTyrePressure和 getRemCharging被定义为属性 TeslaModelS界面。 但是,我们可以为这样的函数定义一个接口:

interface Order {
    (customerId: number, modelId: number): boolean 
}
​
let orderFn: Order = function (cId, mId) {
    // processing the order
    return true // processed successfully!
}

这 orderFn功能是输入 Order. 它需要两个类型的参数 number并返回一个类型的值 boolean.

不需要在定义中再次定义参数的类型 orderFn如您在上面的代码中看到的那样。 编译器只是将接口中定义的参数与函数声明中定义的参数进行一对一的映射。

它推断 cId映射到 customerId它的类型是 number和 mId映射到 modelId它的类型也是 number. 甚至返回类型为 orderFn函数是从接口中的定义推断出来的。

如何在类中使用接口

到目前为止,我们已经了解了函数如何实现接口。 现在让我们为 TeslaModelS界面。

class TeslaModelSPrototype implements TeslaModelS {
    length: number;
    width: number;
    wheelbase: number;
    seatingCapacity: number;
    private tempCache: string;
​
    constructor (l, w, wb, sc) {
        this.length = l;
        this.width = w;
        this.wheelbase = wb;
        this.seatingCapacity = sc;
    }
​
    getTyrePressure () {
        let tyrePressure = 20 // Evaluated after doing a few complex computations!
        return tyrePressure
    }
​
    getRemCharging () {
        let remCharging = 20 // Evaluated after doing a few complex computations!
        return remCharging
    }
}
​
let teslaObj = new TeslaModelSPrototype(196, 86, 116, 4)
console.log('Tyre Pressure', teslaObj.getTyrePressure())

班上 TeslaModelSPrototype定义了接口的所有属性。 请注意,接口仅定义类的公共属性。 从上面的代码可以看出,属性 tempCache有一个访问修饰符 private所以它没有在接口中定义 TeslaModelS.

类中不同类型的变量

一个类具有三种不同类型的变量:

  1. 局部变量:局部变量在函数或块级别定义。 它仅在函数或块执行之前存在。 每次函数运行时,都会在内存中创建局部变量的新副本

  2. 实例变量:实例变量是类的成员。 它们用于存储类对象的属性。 每个对象都有自己的实例变量副本

  3. 静态变量:静态变量也称为类变量,因为它们作为一个整体与一个类相关联。 一个类的所有对象共享同一个静态变量副本

请注意,接口只处理类的实例部分。 例如, constructor功能属于 static部分。 界面 TeslaModelS没有指定任何与 constructor或静态部分。

扩展接口

接口可以扩展任何其他接口并导入其属性。 这有助于构建小型且可重用的组件。 例如,我们可以创建不同的接口来处理特斯拉模型的不同组件,如下所示:

interface Wheel {
    wheelBase: number;
    controls: Array,
    material: string;
}
​
interface Charger {
    adapter: string;
    connector: string;
    location: string;
}
​
interface TeslaModelS extends Wheel, Charger {
    // ... All other properties
}

这 TeslaModelS接口扩展了属性 Wheel和 Charger. 与其将所有属性都转储到单个界面中,不如制作单独的界面来处理不同的组件。

类型别名与接口有何不同?

类型别名用于为 TypeScript 中不同类型的组合命名。

例如,我们可以创建一个类型,它可以是类型 string或者 null:

type StringOrNull = string | null;

类型别名和接口在 TypeScript 中经常互换使用。 的形状 TeslaModelS对象也可以使用定义 type像这样:

type TeslaModelS {
    length: number;
    width: number;
    wheelbase: number;
    seatingCapacity: number;
    getTyrePressure: () => number;
    getRemCharging: () => number;
}

与接口如何使用关键字扩展其他接口和类型别名类似,类型别名也可以使用交集运算符扩展其他类型和接口。 类型别名也可以由类实现。

类型别名通常用于我们必须定义不同类型的合并的情况。 例如,考虑函数 renderObject:

function renderObject (objShape: Square | Rectangle | Triangle) {\
    // ...
}

这 renderObject函数接受一个输入参数 objShape. Square, Rectangle, 和 Triangle是类型,并且 |称为联合运算符。 objShape可以是类型 Square, Rectangle, 或者 Triangle. 但是,形状的联合不能使用界面来表示。

接口用于定义关于对象形状的契约; 因此它们不能与多个形状的并集一起使用。 甚至一个类也无法实现描述形状联合的类型。 这是接口和类型别名之间的重要功能差异之一。

当我们定义两个具有相同名称的接口时,它们都会合并为一个。 生成的接口将具有两个接口的属性。 但是,如果我们尝试定义多个具有相同名称的类型,编译器会报错。

接口中的混合类型

在 JavaScript 中,函数也被视为对象,即使在函数文字上添加属性也是有效的,如下所示:

function manufactureCar (type) {
    const model = function getModel (type) {
        console.log('inside getModel function')
        // get the model of type as mentioned in the argument
    }
    model.getCustomerDetails = function  () {
        console.log('inside customer details function')
        // get the details of the customer who has purchased this model
    }
    model.price = 100000
    model.trackDelivery = function () {
        console.log('inside trackDelivery function')
        // track the delivery of the model
    }
    return model
}
​
let tesla = manufactureCar('tesla')
tesla() // tesla is a function
tesla.getCustomerDetails() // getCustomerDetails is a property defined on function

从上面的代码可以看出,变量 model被赋值为 function/ getCustomerDetails和 trackDelivery作为属性附加在 model. 这是 JavaScript 中的常见模式。 我们如何使用 TypeScript 接口定义这种模式?

interface CarDelivery {
    (string): TeslaModelS,
    getCustomerDetails (): string,
    price: number,
    trackDelivery (): string
}
​
function manufactureCar (type: string): CarDelivery {
    const model =  function (type: string) {
        // get the model of type as mentioned in the argument
    }
    model.getCustomerDetails = function () {
        // get the details of the customer who has purchased this model
        return 'customer details'
    }
    model.price = 100000
    model.trackDelivery = function () {
        // track the delivery of the model
        return 'tracking address'
    }
    return model
}
let tesla = manufactureCar('tesla')
tesla() // tesla is a function
tesla.getCustomerDetails() // getCustomerDetails is a property defined on function

类型的对象 CarDelivery从返回 manufactureCar功能。 界面 CarDelivery有助于保持从返回的对象的形状 manufactureCar功能。 它确保模型的所有强制性属性—— getCustomerDetails, price, 和 trackDelivery— 存在于模型中。

如何在接口中使用泛型

当我们必须创建可以处理多种数据类型的通用组件时,使用 TypeScript 中的泛型。 例如,我们不想将函数限制为仅接受 number作为输入参数。 它应该根据用例进行扩展并接受一系列类型。

让我们编写代码来实现处理通用数据类型的堆栈:

interface StackSpec {
    (elements: Array): void
}
​
function Stack (elements) {
    this.elements = elements
    this.head = elements.length - 1
​
    this.push = function (number): void {
        this.elements[this.head] = number
        this.head++
    }
​
    this.pop = function (): T {
        this.elements.splice(-1, 1)
        this.head--
        return this.elements[this.head]
    }
​
    this.getElements = function (): Array {
        return this.elements
    }
}
​
let stacksOfStr: StackSpec = Stack
let cars = new stacksOfStr(['Hatchback', 'Sedan', 'Land Rover'])
cars.push('Tesla Model S')
​
console.log('Cars', cars.getElements()) // ['Hatchback', 'Sedan', 'Land Rover', 'Tesla Model S']

界面 StackSpec接受任何数据类型并将其放入函数的定义中。 T用于定义 type. 功能 Stack将元素数组作为输入。

Stack有方法—— push用于添加类型的新元素 T在原来的 elements大批, pop用于删除最顶层的元素 elements数组,并且 getElements函数返回类型的所有元素 T.

我们创建了一个字符串堆栈,称为 stacksOfStr,其中包含 string并相应地替换 T和 string. 我们可以重用这个堆栈实现来创建堆栈 number和其他数据类型。

我们还可以创建一堆特斯拉模型。 让我们看看如何做到这一点:

let stacksOfTesla: StackSpec = Stack
let teslaModels = [
    {
        engineer: 'John',
        modelId: 1,
        length: 112,
        //...
    },
    // ...
]
let teslaStack = new stacksOfTesla(teslaModels)
console.log(teslaStack) // prints the value of `teslaModels`

请注意,我们对类型数组使用相同的堆栈实现 TeslaModelS. 泛型与接口相结合是 TypeScript 中的强大工具。

TypeScript 如何编译接口

TypeScript 在处理 JavaScript 的奇怪部分方面做得很好。 但是,浏览器不理解 TypeScript,因此必须将其编译为 JavaScript。

TypeScript 编译器编译上述内容 TeslaModelSPrototype类为:

var TeslaModelSPrototype = /** @class */ (function () {
    function TeslaModelSPrototype(l, w, wb, sc) {
        this.length = l;
        this.width = w;
        this.wheelbase = wb;
        this.seatingCapacity = sc;
    }
    TeslaModelSPrototype.prototype.getTyrePressure = function () {
        var tyrePressure = 20; // Evaluated after doing a few complex computations!
        return tyrePressure;
    };
    TeslaModelSPrototype.prototype.getRemCharging = function () {
        var remCharging = 20; // Evaluated after doing a few complex computations!
        return remCharging;
    };
    return TeslaModelSPrototype;
}());
var teslaObj = new TeslaModelSPrototype(196, 86, 116, 4);
console.log('Tyre Pressure', teslaObj.getTyrePressure());

我正在使用 TypeScript Playground 来查看编译后的代码。 实例变量—— length, width, wheelBase, 和 seatingCapacity— 在函数中初始化 TeslaModelSPrototype. 方法 getTyrePressure和 getRemCharging在函数的原型上定义 TeslaModelSPrototype.

上面的代码是纯 JavaScript,所以它可以在浏览器中运行。

简历模板生成器VIP版App,所有VIP简历模板免费使用,一键自动生成傻瓜式操作!

为什么要使用接口?

正如您已经了解到的,接口有助于定义实体实现的具体计划。 除此之外,接口还有助于 JavaScript 引擎的性能。

本节假设您对 JavaScript 引擎有一定的了解。 在本节中,我们将深入研究 JavaScript 引擎的工作原理,并了解接口如何帮助提高性能。

让我们了解 V8(Chrome 上的 JavaScript 引擎)上 的编译器如何存储对象。

TypeScript 中的接口只存在于编译时。 正如您在上面由 TypeScript 编译器生成的代码中看到的那样,没有提及接口。 的属性 TeslaModelS界面 ( length, width, wheelBase, 和 seatingCapacity) 被添加到 TeslaModelSPrototype构造函数,而函数类型附加在原型上 TeslaModelSPrototype功能。 JavaScript 引擎不知道与接口相关的任何事情。

如果我们实例化数千个 TeslaModelSPrototype汽车,我们将不得不处理数以千计的类型对象 TeslaModelS. 这些对象中的每一个都将具有类似于接口的结构。

JavaScript 引擎如何存储这数千个形状相同的对象? 它会制作数千个这些对象的副本吗? 制作数千份类似形状的副本绝对是浪费内存。

JavaScript 引擎只生成一种类型的类型 TeslaModelS并且每个对象只存储定义的属性的相应值 TeslaModelS界面。

对象共享相同的形状。

这对 JavaScript 引擎来说是一个巨大的性能优势。

如果对象具有不同的形状,引擎将不得不为这些对象创建不同的形状并相应地处理它们。 界面有助于保持相似对象的形状完整。

可调用接口

可调用接口允许我们使用类型函数来捕捉问题,例如传递函数期望错误的参数或返回错误的值。

例如,采用以下代码:

const generateANumber: Function = (factor: number, genFunc: Function) => {
return genFunc(factor)
}
console.log(generateANumber(5, (a:number) => a)) // 5
console.log(generateANumber(5, () => "Cheese")) //Cheese

假设我们想要 getFunc参数是一个接受数字并返回数字的函数。 上面的代码没有强制执行,我们可以看到在第 5 行,我们传递了一个不带参数并返回字符串的函数。 不会引发类型错误。

我们可以这样解决问题:

// Callable Interface
interface NumberGenerator {
    (num: number): number
}
interface GenANum {
    (num: number, gen: NumberGenerator): number
}
const generateANumber: GenANum = (factor: number, genFunc: NumberGenerator) => {
    return genFunc(factor)
}
console.log(generateANumber(5, (a:number) => a)) // 5
console.log(generateANumber(5, () => "Cheese")) // Type Error

我们有两个接口为 generateANumber及其 genFunc争论。 这将通过 () =>``" Cheese "到 generateANumber函数并给我们一个类型错误,从而使我们的代码更加类型安全。 可调用接口本质上允许我们键入我们的函数。

您可以在此处查看本节的代码 。

如何在 React 中使用接口

让我们构建一个使用 React 和 TypeScript 接口显示 Pokemon 列表的简单用例。

这是主要的 App在 div 容器中呈现 Pokemon 列表的组件,其 id 为 root:

import React, { Component, Fragment } from 'react';
import { render } from 'react-dom';
import PokemonList from './pokemon-list';
import './style.css';
​
const App = () => {
  return (
    
        

Pokemon List

             
) } ​ render(, document.getElementById('root'));

The App component renders PokemonList.

让我们检查一下实现 PokemonList零件:

import React, { Component } from 'react';
import { PokemonListModel } from './pokemon-model';

interface PokemonProps {}
interface PokemonState {
  pokemonList: PokemonListModel | null;
}

class PokemonList extends Component {

  constructor (props) {
    super(props);
    this.state = {
      pokemonList: null
    }
  }

  getPokemonList = () => {
    fetch ('https://pokeapi.co/api/v2/pokemon/?limit=50')
      .then (response => {
        return response.json();
      })
      .then (response => {
        this.setState({ pokemonList: response });
      })
  }

  render () {
    let { pokemonList } = this.state;
    return (
      
{ pokemonList && pokemonList.results.map (pokemon => { return (
{pokemon.name}
) }) }
) } componentDidMount () { this.getPokemonList() } } export default PokemonList

这 PokemonList获取 Pokemon 列表 组件使用开源 Poke API 项目 。 它将 Pokemon API 的结果存储在组件的状态中。 组件使用接口 PokemonProps和 PokemonState用于定义其 props和 state. 界面 PokemonListModel定义从 Pokemon API 返回的对象的结构。

这是 PokemonListModel界面:

export interface PokemonListModel {
  count: number;
  next: string | null;
  previous: string | null;
  results: Array
}

interface Pokemon {
  name: string;
  url: string;
}

注意类型 results财产。 它使用接口 Pokemon定义结构 results. 这是 Stackblitz 上 Pokemon 应用程序的演示:

在 Angular 中使用接口

现在,让我们深入了解 Angular 中的接口。 由于 Angular 是一个基于 TypeScript 的框架,Angular 中的接口工作类似,但仍然值得讨论。 让我们检查以下 Angular 代码库以了解其对接口的使用。

首先,我们来看看 /src/app/types.ts. 在这个文件中,我们可以定义我们计划在整个应用程序中使用的任何接口和类型。

export interface Post {
  title: string
  content: string
}

在这里,我们定义一个单一的接口 Post, 有两个性质 title和 content. 我们计划能够检索帖子列表并能够添加帖子。 我们希望将这种数据管理封装到一个服务中,我们可以在 /src/app/post.service.ts.

import { Injectable } from '@angular/core';
import { Post } from './types';
@Injectable({
  providedIn: 'root'
})
export class PostService {
  #posts:[Post] // private property of posts
  constructor() {
    this.#posts = [{title: "Post 0", content: "content for post 0"}]
   }
  // method for adding a post
  addPosts(newPost: Post){
    this.#posts.push(newPost)
  }
  // method for getting all posts
  getPosts(){
    return this.#posts
  }
}

在这个服务调用中,我们使用了我们的 Post接口来定义一个私有属性,一个帖子数组,然后用一个初始帖子实例化这个数组。 我们也给这个服务两个方法:

  • addPosts: 需要一个 Post作为一个论点,然后将其推入 this.#posts大批

  • getPosts: 返回数组 Post对象

然后我们可以将这个服务注入到任何需要访问这个帖子列表的组件中,比如我们的 App成分在 src/app/app.component.ts:

import { Component } from '@angular/core';
import { PostService } from './post.service';
import { Post } from './types';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'sampleproject';
  postService: PostService;
  posts: [Post]
  // initiate PostService in component
  constructor(postService: PostService) {
    this.postService = postService;
    this.posts = this.postService.getPosts()
  }
  addPost() {
    // Create new post
    const newPost: Post = {
      title: `Post ${this.posts.length}`,
      content: 'this is the content for this post',
    };
    // update list of posts in service and component
    this.postService.addPosts(newPost)
    this.posts = this.postService.getPosts()
  }
}

在里面 Component类,我们使用我们的服务和帖子类型来定义类的两个属性:

  • this.postService: 的实例 PostService我们之前定义的

  • this.posts: 数组的副本 Post此组件的本地对象

在构造函数中,我们使用依赖注入来实例化一个 PostService在构造函数参数中。 我们分配 postService, 与 PostService反对 this.postService然后立即使用它的 getPosts()用现有帖子填充数组的方法。

我们添加一个本地 addPost以编程方式添加新帖子并更新数组的服务和本地版本的组件的方法。 完成所有这些后,我们现在可以为该组件构建接口 src/app/app.component.html.

Posts

 

{{post.title}}

 

{{post.content}}

在这里,我们有 div循环 this.posts组件中的数组并生成一个 h2和 p每个帖子的元素。 我们还有一个按钮可以触发 addPost方法并添加新帖子。

在所有这些过程中,我们一直在使用 Post界面。 如果我们犯了任何错误,比如写一个 Post财产作为 Title代替 title,我们会得到类型错误,因此我们可以在应用程序运行之前修复问题,最终避免运行时错误。

结论

接口是在 TypeScript 中定义合约的一种强大方式。 让我们回顾一下我们在本教程中学到的所有内容:

  1. 接口定义实体的规范,它们可以通过函数或类来实现。 我们可以使用在接口上定义可选属性 ?和只读属性通过使用 readonly属性名称中的关键字

  2. TypeScript 编译器还会检查对象上的多余属性,如果对象包含在接口中定义的属性,则会给出错误

  3. 我们还学习了如何使用接口定义 Indexable 属性

  4. 类可以实现一个接口。 该接口仅包含类的实例变量的定义

  5. 可以扩展接口以导入其他接口的属性,使用 extends关键词

  6. 我们可以通过接口使用泛型的力量并构建可重用的组件

  7. 我们还了解了接口如何帮助提高 JavaScript 引擎的性能

你可能感兴趣的:(typescript,javascript,开发语言)