Что такое интерфейсы и зачем они вообще нужны? TypeScript ввел новый тип данных, определяемый с помощью конструкции называемой "interface"
. Интерфейсы в TypeScript служат для именования типов данных, и являются способом определения соглашений внутри кода. Другими словами, создавая интерфейс мы создаем некоторый тип данных, который в основном служит для объектов или классов, где мы указываем какие поля, какие функции и какие вообще элементы должны присутствовать у этих объектов. Давайте рассмотрим вот такой простой пример:
interface IUser {
readonly id: number,
name: string,
color?: string,
size:{
width: number,
height: number
}
}
Некоторые свойства должны изменяться только один раз, при создании объекта. Этого можно добиться, ограничив свойство только для чтения, применив модификатор “readonly”
, как мы с делали это полем “id”
.
Не всегда все свойства интерфейса должны являться обязательными. Сделать свойство необязательным для заполнения путем добавления знака “?”
после его именования, как это сделано у поля “color”
в примере.
Также в интерфейсе можно указывать, какие-то другие объекты, как это сделано у поля “size”
.
После создания интерфейса, для нас открывается возможность создавать различные объекты которые будут являться типом нашего интерфейса, а именно “IUser”
. Давайте
посмотрим на примере как это работает:
const user: IUser = {
id: 123,
name: 'Чак Норрис',
color: '#ccc',
size:{
width: 20,
height: 30
}
}
В примере мы создали переменную “user”
, указав ее тип “IUser”
. В следующем примере мы проделаем подобные действия, но уже не добавляя свойство “color”
, так как оно указано как не обязательное, но добавляя его ниже созданной переменной.
const user: IUser = {
id: 123,
name: 'Чак Норрис',
size:{
width: 20,
height: 30
}
}
user.color = “red”
Единственное, что здесь может смутить, это то что переменная “user”
, является константой, а в последствии мы ее изменяем. Но в конечном итоге, так как, мы работаем в JavaScript, то мы можем изменять внутреннее состояние констант, если они является объектом или массивом.
Так же мы можем указывать к какому типу будет относиться объект. Вот два примера:
const user = {} as IUser
const user1 = <IUser> {} // более старая запись
Наследование (или расширение) интерфейсов
TypeScript позволяет, создавать интерфейсы включающие в себя комбинации других интерфейсов, что позволяет настроить очень гибкое взаимодействие между интерфейсами. Давайте рассмотрим это на примере:
interface IUserWidthArea extends IUser {
getArea:()=> number
}
const user2: IUserWidthArea = {
id: 123,
name: 'Чак Норрис',
size:{
width: 20,
height: 30
},
getArea(): number{
return this.size.width * this.size. height
}
}
Интерфейс, который мы создали будет наследоваться от интерфейса “IUser”
. В него мы можем добавлять какие-то новые поля. В нашем случае мы обязываем его реализовывать функцию “getArea()”
, которая будет рассчитывать площадь.
Так же интерфейсы могут взаимодействовать с классами. Давайте
это рассмотрим на примере интерфейса “IClock”
:
interface IClock{
time: Date,
setTime(date: Date): void
}
сlass Clock implements IClock {
time: Date = new Date()
setTime(date: Date): void{
this.time = date
}
}
Еще что бы я хотел рассказать об интерфейса в рамках это статья, это то что бывают ситуации, когда необходимо создать интерфейс для объекта у которого будет большое количество динамических ключей, например:
const css = {
padding: '10px 40px 10px 40px',
position: 'relative',
margin: '10px 60px 10px 60px',
height: '90px',
// и так далее
}
Создавая интерфейс для данный переменной мы не можем перечислить всевозможные свойства, это будет крайне неэффективно, поэтому для таких ситуаций существует специальный синтаксис:
interface IStyles{
[key:string]: string
}
Где в квадратных скобках мы указываем тип ключа и тип его значения.