Как типизировать объект в зависимости от указанного значения в свойстве?
Есть общий интерфейс(SpinnerInterface) в котором перечислены все допустимые свойства объекта. Но свойство(statusCheck) должно быть обязательным только в случае если type будет указан как progressLoading
interface SpinnerInterface < fillParent: boolean; type: SpinnerType; statusCheck: StatusProgress; // только для type:progressLoading >enum SpinnerType
Типизация через Omit исключает свойство для всех свойств.
type ExcludeStatus = Omit;
Я предполагаю что нужно добавить еще какую то обработку перед ExcludeStatus но я незнаю в какую сторону нужно копать(гуглить). Буду рад вашим идеям/предположениям/ссылкам. Большое спасибо за помощь.
Отслеживать
задан 9 июл 2021 в 9:08
Ihor Snisar Ihor Snisar
3 1 1 бронзовый знак
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Можно создать общий тип с кастингом св-ва + несколько типов, которые расширяют базовый и там прописать условие для динамических полей
enum SpinnerType < BASE = 'base', PROGRESS_LOADING = 'progressLoading', >interface StatusProgress < loading: boolean; >// Базовый тип interface Spinner < fillParent: boolean; type: T; >interface SpinnerBase extends Spinner <>; interface SpinnerWithProgress extends Spinner < statusCheck: StatusProgress; >type Spinners = SpinnerBase | SpinnerWithProgress; // Тут все ок без св-ва statusCheck const spinnerBase: SpinnerBase = < type: SpinnerType.BASE, fillParent: true, >// Тут ошибка, что statusCheck св-ва нет const spinnerWithProgressWrong: SpinnerWithProgress = < type: SpinnerType.PROGRESS_LOADING, fillParent: false, >const spinnerWithProgressRight: SpinnerWithProgress = < type: SpinnerType.PROGRESS_LOADING, fillParent: false, statusCheck: < loading: false >>
Из плюсов, в switch/case по type тип будет автоматические внутри case определяться)
Как типизировать объект typescript
Кроме простых типов данных, как и в javascript, можно создавать сложные комплексные объекты, которые состоят из других объектов и примитивных данных. Например:
let person = ; console.log(person.name); // альтернативный вариант получения свойства console.log(person["name"]);
Но несмотря на то, что это фактически тот же самый объект, что мы могли бы использовать в JavaScript, в силу строготипизированности TS мы имеем в данном случае ограничения. В частности, если у нас будет следующий код:
let person = < name: "Tom", age: 23 >; person = < name: "Bob" >; // ! Ошибка
То на второй строке мы получим ошибку, поскольку компилятор после первой строки предполагает, что объект person будет иметь два свойства name и age, которые имеют тип string и number соответственно. То есть в данном случае переменная person представляет тип < name: string; age: number >. И мы могли написать так:
let person: < name: string; age: number >= < name: "Tom", age: 23 >; console.log(person.name);
Поэтому данной переменной мы можем присвоить другой объект, который соответствует типу < name: string; age: number >по названиям, количеству и типу свойств, как в следующем случае
let person = < name: "Tom", age: 23 >; person = < name: "Bob", age: 35 >; // Норм
Необязательные свойства
TypeScript позволяет сделать свойства необязательными. Для этого после названия свойства указывается знак вопроса ?
let person: < name: string; age?: number >; // Свойство age - необязательное
В данном случае свойство age является необязательным, поэтому мы можем не предоставлять для него значение:
let person: < name: string; age?: number >; // Свойство age - необязательное person = < name: "Tom", age: 23 >; console.log(person.name); // Tom person = < name: "Bob">; // Норм, свойство age - необязательное console.log(person.name); // Bob
При обращении к неустановленному свойству мы получим undefined :
let person: < name: string; age?: number >= < name: "Tom", age: 23 >; console.log(person.age); // 23 person = < name: "Bob">; console.log(person.age); // undefined
Поэтому при операциях с таким свойством мы можем проверять его на значение undefined :
let person: < name: string; age?: number >= < name: "Tom", age: 36>; if (person.age !== undefined)
Объекты в функциях
Функция может принимать объекты в качестве параметров и могут возвращать объект. В этих случаях необходимо указать тип объекта для параметров и результата функции. Например, объект как параметр функции:
function printUser(user: < name: string; age: number>) < console.log(`name: $age: $`); > let tom = ; printUser(tom);
В данном случае параметр функции printUser представляет тип < name: string; age: number>— то есть объект, который имеет два свойства типов string и number.
При этом объект, передаваемый в качестве параметра, может быть более широким — содержать больше свойств, но главное, чтобы он содержал те свойства, которые предусмотрены для параметра функции:
function printUser(user: < name: string; age: number>) < console.log(`name: $age: $`); > let bob = ; printUser(bob);
Здесь переменная bob представляет тип , тем не менее он также соответствует определению типа < name: string; age: number>.
Объект как результат функции:
function defaultUser(): < name: string; age: number>< return ; > let user = defaultUser(); console.log(`name: $ age: $`);
Оператор in
Оператор in позволяет проверить наличие определенного свойства в объекта. Он возвращает true , если свойство есть в объекте, и false , если свойство отсутствует. Например:
let tom: < name: string; age?: number >= < name: "Tom", age: 23 >; let bob: < name: string; age?: number >= < name: "Bob">; function printUser(user: < name: string; age?: number >) < if("age" in user)< console.log(`Name: $Age: $`); > else< console.log(`Name: $`); > > printUser(tom); printUser(bob);
Здесь функция printUser() принимает объект типа < name: string; age?: number >, то есть свойство age в таком объкте может присутствовать, а может отсутствовать. Поэтому применяем оператор in для проверки наличия свойства:
if("age" in user)
Название свойства передается как строка.
Декомпозиция объектов-параметров
Если функция принимает объект в качестве параметра, то TypeScript позволяет автоматически разложить его на свойства:
function printUser(: < name: string; age: number>) < console.log(`name: $age: $`); > let tom = ; printUser(tom);
Здесь функция printUser() в качестве параметра принимает объект из двух свойств. Запись указывает, что свойства объекта автоматически будут разложены на два параметра: name и age. Главное, чтобы названия этих параметров соответствовали названиям свойств объекта.
Некоторые параметры могут необязательными:
function printUser(: < name: string; age?: number>) < if(age!==undefined)< console.log(`name: $age: $`);> else > let tom = ; printUser(tom); // name: Tom let bob = ; printUser(bob); // name: Bob age: 44
В данном случае параметр age является необязательным. Кроме того, имеет значение по умолчанию. Поэтому функция может принимать объект без свойства age.
Также они могут принимать значения по умолчанию:
function printUser(: < name: string; age?: number>) < console.log(`name: $age: $`); > let tom = ; printUser(tom); // name: Tom age: 25 let bob = ; printUser(bob); // name: Bob age: 44
TypeScript. Интерфейсы
В предыдущей статье по TypeScript мы познакомились с основными типами данных языка, а так же с возможностью создавать собственные примитивные типы.
Как гласит документация к языку, одним из основных принципов TypeScript является то, что типизация основана на структуре (shape) объектов. Такой способ типизации называют неявной или «утиной» — объект относят к тому или иному типу (классу, интерфейсу), если он имеет (реализует) все его свойства и методы. Интерфейсы в TS применяются как раз для того, чтобы описывать нужные вам типы.
Например, у нас есть функция callPet:
function callPet (pet) < console.log(`Hey, $, come here!`); >
Она принимает в качестве аргумента объект питомца. Опишем его с помощью интерфейса:
interface Pet < name: string; age: number; color: string; >
Питомец имеет имя, возраст и окрас.
Теперь укажем, что callPet принимает именно объект питомца. И вызовем функцию:
function callPet (pet: Pet): void < console.log(`Hey, $, come here!`); > callPet()
Заметим, что объект питомца создан “на месте”. Нигде не указано, что он реализует интерфейс Pet, проверка происходит только на соответствие свойств в самой функции callPet.
Если в передаваемый объект добавить свойство, которого нет в интерфейсе или, наоборот, убрать одно из описанных, компилятор TS выдаст ошибку о несоответствии типов. Это и есть неявная или «утиная» типизация.
Этот пример показывает основное назначение и использование интерфейсов в TS, далее мы рассмотрим другие возможности по работе с собственными типами в TypeScript.
Реализация интерфейсов
Как и во многих языках программирования, поддерживающих парадигму ООП, классы в TS могут “реализовывать” (implements) интерфейсы. Продолжим работать с интерфейсом Pet и опишем его поля более подробно:
interface Pet < name: string; readonly age: number; readonly color: string; readonly bday: Date; toy?: string; >
Возраст и окрас питомца не зависит от внешних факторов, поэтому сделаем эти свойства readonly. Если попытаться записать в такие свойства значения, компилятор выдаст ошибку. Так же добавим свойство bday, чтобы потом иметь возможность вычислить возраст. Свойство toy обозначено как необязательное с помощью ?. Поэтому если не указать его в классе, реализующем интерфейс, код скомпилируется без ошибок.
Теперь реализуем интерфейс Pet классом Dog:
class Dog implements Pet < name: string; readonly color: string; readonly bday: Date; constructor (name: string, color: string, bday: Date) < this.name = name; this.color = color; this.bday = bday; >get age() < const diff = new Date(new Date().getTime() - this.bday.getTime()); return diff.getFullYear() - new Date(0).getFullYear(); >> const bobby = new Dog('Bobby', 'black', new Date(2010, 4, 12));
Если не реализовать в классе какое-либо свойство интерфейса, компилятор выдаст ошибку.
Note: Наличие модификатора readonly не проверяется, поэтому его нужно не забывать указывать самостоятельно. Для методов-геттеров он учитывается по умолчанию.
Методы
Помимо свойств, интерфейсы могут описывать методы классов:
interface Pet < speak(): void; run(meters: number): void; bringToy?(toy?: string): string; >class Dog implements Pet < speak(): void < console.log('Bark!'); >run(meters: number): void < console.log(`$ran for $ meters`); > >
Наследование интерфейсов
Интерфейсы в TS могут наследовать друг друга. Например, мы можем сделать Dog не классом, а интерфейсом с полями, которые будут дополнять интерфейс Pet:
interface Dog implements Pet < readonly breed: string; >
Стоит заметить, что одновременно могут существовать и класс Dog, и интерфейс Dog. TypeScript сам разрешит конфликт имен при компиляции, поскольку интерфейсы в скомпилированный .js файл не входят.
Реализацию интерфейса Dog теперь будет выполнять классы для каждой породы. Чтобы не писать все методы заново, можно просто наследовать класс от класса Dog и реализовать им интерфейс Dog:
class GermanShepherd extends Dog implements Dog < readonly breed: string = 'German Shepherd'; >class Dalmatian extends Dog implements Dog < /* При указании значения свойства сразу при объявении, можно не указывать тип */ readonly breed = 'Dalmatian'; >const rex = new GermmanShepherd('Rex', 'black', new Date(2014, 2, 10)); const pongo = new Dalmatian('Pongo', 'dotted', new Date(2012, 7, 3));
Интерфейсы могут наследовать и классы. При наследовании от класса, интерфейс наследует все его свойства и методы (в том числе private и protected), но не наследует их значение и реализацию. Поскольку наследуются приватные свойства, такой интерфейс может реализовать только подкласс исходного класса.
Типизация массивов
Интерфейсы в TS позволяют задать статическую типизацию для индексируемых типов. Рассмотрим это на простом примере:
interface Shelter < [index: number]: Pet; >
Выше написано примерно следующее: тип Shelter это объект с числовыми индексами, по которым находятся объекты типа Pet. То есть Shelter, по сути, массив питомцев. Можно реализовывать индексацию самостоятельно, но гораздо проще наследовать класс от встроенного класса Array. Для того, чтобы компилятор понял, значения какого типа будут храниться в массиве, укажем его при наследовании: Array. Пример реализации интерфейса Shelter:
class DogShelter extends Array<Dog> implements Shelter < constructor(. dogs: Dog[]) < super(. dogs); >> const shelter = new DogShelter(bobby, rex, pongo); shelter.forEach(dog => dog.speak());
Множественная реализация
Класс может реализовывать больше одного интерфейса. Это бывает удобно, когда нужно логически разделить свойства сложного объекта. Например DogShelter помимо Shelter может реализовывать интерфейс Building:
interface Building < address: string; type: string; >class DogShelter extends Array<Dog> implements Shelter, Building < address: string; type: string; constructor(addess: string, . dogs: Dog[]) < super(. dogs); this.address = address; this.type = 'shelter' >>
Типизация функций
Интерфейсы могут быть использованы для того, чтобы описать функцию:
interface Build < (address: string, type: string): Building; >
Теперь можно строго определить тип функции с помощью созданного интерфейса:
let buildHouse: Build; buildHouse = function (address: string, type: string) < return
>; const WinterPalace = buildHouse('Palace Square', 'palace');
Гибридные типы
Интерфейс может совместить в себе описание и функции, и объекта, и индексируемого типа:
interface Build < /* Описание функции */ (name: string, address: string, type: string): Building; /* Описание полей объекта */ developer: string; started: Date; /* Индексируемый тип */ buildings: < [buildingName: string]: Building >> function DevelopingProject(developer: string, started: Date) < let project = <Build> function ( name: string, address: string, type: string ): Building < const building: Building =
; project.buildings[name] = building; return building; > project.buildings = <>; project.developer = developer; project.started = started; return project; > const SPb = DevelopingProject('Peter the Great', new Date(1703)); const PeterAndPaulsFortress = SPb('Peter and Paul`s Fortress', 'Rabbit Island', 'fortress');
Главное
Интерфейсы в TS полезный и очень удобный инструмент для создания собственных типов данных. Они отлично подходят для описания классов и объектов и помогают избежать ошибок, связанных с неявными присваиваниями и преобразованиями.
С помощью интерфейсов, можно строго задавать структуру приложения. Например, популярный фреймворк Angular версии 2.0 использует интерфейсы, чтобы описывать жизненный цикл компонентов, а умные IDE умеют распознавать и подсказывать ошибки еще на уровне разработки, до компиляции.
Но важно помнить, что интерфейсы TS сильно отличаются от привычных интерфейсов ООП — они выполняют только функции пользовательских типов и не более. Именно поэтому они могут наследовать классы и другие интерфейсы.
If you like this article, share a link with your friends
Read more
We talk about interesting technologies and share our experience of using them.
Typescript. Тип object
В TypeScript версии 2.2 был введён новый тип object . Он описывает любой непримитивный тип.
Следующие типы принято считать примитивными в JavaScript :
Все остальные типы принято считать непримитивными.
Новый тип object представляет именно их:
// All primitive types type Primitive = | boolean | number | string | symbol | null | undefined; // All non-primitive types type NonPrimitive = object;
Давайте посмотрим, как object поможет нам более точно описывать типы.
Описания типов, использующие тип object
С релизом TypeScript версии 2.2 описания типов стандартной библиотеки были обновлены с использованием нового типа object . Например, методы Object.create() и Object.setPrototypeOf() теперь описывают параметр прототипа, как object | null :
interface ObjectConstructor < /** * Creates an object that has the specified prototype or that has null prototype. * @param o Object to use as a prototype. May be null. */ create(o: object | null): any; /** * Sets the prototype of a specified object o to object proto or null. Returns the object o. * @param o The object to change its prototype. * @param proto The value of the new prototype or null. */ setPrototypeOf(o: any, proto: object | null): any; // . >
Если передать примитивный тип, в качестве прототипа в Object.setPrototypeOf() или в Object.create() , то, во время исполнения кода, будет выброшено исключение TypeError . Теперь TypeScript отлавливает такие ошибки на этапе компиляции:
const proto = <>; Object.create(proto); // OK Object.create(null); // OK Object.create(undefined); // Error Object.create(1337); // Error Object.create(true); // Error Object.create("oops"); // Error
Другим местом для применения типа object является структура данных WeakMap . Ключи которой должны быть объектами и не могут являться примитивами. Это требование нашло отражение в тайпингах:
interface WeakMap
object vs Object vs <>
Возможно Вас смутит, что TypeScript определяет несколько типов, имеющих похожие имена, но представляющие разные концепции:
Мы рассмотрели новый тип object выше. Теперь давайте обсудим, что же из себя представляют Object и <> .
Тип Object
Typescript определяет другой тип, с почти таким же названием, как и новый тип object , и это тип Object . В то время как object (с маленькой буквы) представляет все непримитивные типы, Object (с большой буквы) описывает функциональность, общую для все JavaScript объектов. Например, методы toString() и hasOwnProperty() . В файле lib.es6.d.ts тип Object определяется следующим образом:
interface Object < // . /** Returns a string representation of an object. */ toString(): string; /** Returns a date converted to a string using the current locale. */ toLocaleString(): string; /** Returns the primitive value of the specified object. */ valueOf(): Object; /** * Determines whether an object has a property with the specified name. * @param v A property name. */ hasOwnProperty(v: string): boolean; /** * Determines whether an object exists in another object's prototype chain. * @param v Another object whose prototype chain is to be checked. */ isPrototypeOf(v: Object): boolean; /** * Determines whether a specified property is enumerable. * @param v A property name. */ propertyIsEnumerable(v: string): boolean; >
Есть ещё один тип, который очень похож: <> , пустой тип объекта. Он описывает объект у которого нет собственных свойств. При попытке доступа к произвольным свойствам такого объекта TypeScript выдаёт ошибку во время компиляции:
// Type <> const obj = <>; // Error: Property 'prop' does not exist on type '<>'. obj.prop = "value";
Тем не менее, вы можете использовать все свойства и методы, описанные в типе Object , которые неявно доступны через цепочку прототипов:
// Type <> const obj = <>; // "[object Object]" obj.toString();
- javascript
- frontend
- frontend-разработка
- typescript
