Русский
Русский
English
Статистика
Реклама

Recovery mode Разбираем классы по косточкам или интроспектируем типы в Typescript


Крутую ты штуку придумал, Стёпа, сообщил мне коллега, осознав рассказанную ему идею. Надеюсь это действительно так, хоть и не скажу, что в том, о чём далее пойдёт речь, есть что-то безумно новаторское, однако, на мой взгляд, интерес данный материал всё же представляет.
Сегодня поговорим о применении интроспекции в разработке веб-интерфейсов, немного пошаманим с обобщённым программированием и изобретём велосипед в Typescript, имеющий похожий аналог в .NET.


Что мы знаем об интроспекции?


Википедия гласит, что это возможность запросить тип и структуру объекта во время выполнения программы.


Ну то есть имеется класс:


class Person {    height: number;    weight: number;    bloodPressure: string;}

Его объект определяется набором полей, каждое из которых имеет, по крайней мере, свой тип и значение.



При этом данный массив мы можем получить в любой момент выполнения программы, вызвав какую-то функцию.


const fields = ObjectFields.of(Person)

От теории к практике


Я, конечно, человек с замыленными мозгами, но в данной ситуации буду мыслить шаблонно. Вытянуть имена полей можно с помощью Object.keys, а типизировать это дело уже через keyof. Далее используя ключи, как индексы, получаем значения и данные о них.
Пропустив через себя эту информацию, можно выразить свои выводы следующим образом. Начнём с простого, описав некий тип, характеризующий поле объекта.


interface IObjectField<T extends object> {    readonly field: keyof T;    readonly type: string;    readonly value: any;}

Если задуматься, то можно увидеть, что это сильно напоминает FieldInfo. Правда я это понял в момент написания статьи :)
И сейчас самое время вспомнить, что Typescript это не .NET. Например, создавать экземпляры объекта в контексте обобщённого программирования здесь можно только с помощью фабрик. То есть, как в C# не прокатит.
Если описывать конструктор некого класса, то получится приблизительно следующее.


interface IConstructor<T> {    new(...args: any[]): T;}

Хорошо, попробуем создать инструмент для интроспекции класса, который бы удовлетворял следующим требованиям:


  1. Всё, что у нас есть на входе это конструктор изучаемого класса.
  2. На выходе мы получаем массив объектов типа IObjectField
  3. Сгенерированные данные неизменяемы.

Вот теперь рассуждения в начале раздела переведены на язык Typescript.


class ObjectFields<T extends object> extends Array<IObjectField<T>> {    readonly [n: number]: IObjectField<T>;    constructor(type: IConstructor<T>) {        const instance: T = new type();        const fields: Array<IObjectField<T>> = (Object.keys(instance) as Array<keyof T>)            .map(x => {                const valueType = typeof instance[x];                let result: IObjectField<T> = {                    field: x,                    type: valueType === 'object'                        ? (instance[x] as unknown as object).constructor.name                        : valueType,                    value: instance[x]                }                return result;            });        super(...fields);    }}

Попробуем "прочитать" класс Person и выведем данные на экран.


const fields = new ObjectFields(Person);console.log(fields);

Правда, вместо ожидаемого вывода получили пустой массив.



Как же так? Всё скомпилировалось и отработало без ошибок. Однако дело в том, что результирующий массив строится с помощью Object.keys, и поскольку в рантайме работает Javascript, то какой объект засунем, такой набор ключей и получим. А объект пустой, вот и информация о типах, которую мы попытались извлечь, куда-то потерялась. Чтобы её "вернуть", необходимо инициализировать поля класса какими-то начальными значениями.


class Person {    height: number = 80;    weight: number = 188;    bloodPressure: string = '120-130 / 80-85';}

Вуаля получили, что хотели.



Также протестируем более сложную ситуацию.


class Material {    name = "wood";}class MyTableClass {    id = 1;    title = "";    isDeleted = false;    createdAt = new Date();    material = new Material();}

Результат превзошёл ожидания.



И что с этим делать?


Первое, что пришло в голову: CRUD приложения на react теперь можно писать, реализуя обобщённые компоненты. Например, нужно сделать форму для вставки в таблицу. Пожалуйста, никто не запрещает делать что-то такое.


interface ITypedFormProps<T extends object> {    type: IConstructor<T>;}function TypedForm<T extends object>(props: ITypedFormProps<T>) {    return (        <form>            {new ObjectFields(props.type).map(f => mapFieldToInput(f))}        </form>    );}

И использовать потом этот компонент вот так.


<TypedForm    type={Person} />

Ну и саму таблицу сделать по такому же принципу тоже возможно.


Подводя итоги


Хочется сказать, что штука получилась интересная, но пока непонятно, что с ней делать дальше. Если вам было интересно или есть какие-либо предложения, пишите в комментариях, а пока до новых встреч! Спасибо за внимание!

Источник: habr.com
К списку статей
Опубликовано: 03.01.2021 02:10:08
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Программирование

Ооп

Typescript

Интроспекция

Обобщенное программирование

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru