Комментарии 6
Привет, есть вопрос по типизации, не очень понятно зачем расширять ручками интерфейсы, если тип к emitEvent можно высчитать автоматически
что я имею ввиду
const OUR_FIRST_ONBOARDING = {
id: "НАШ_ПЕРВЫЙ_ОНБОРДИНГ",
steps: [
{
id: "ID_ПЕРВОГО_ШАГА",
type: StepTypes.CLICK,
views: {
pointer: {
type: PointerType.CANVAS,
elementId: 'ЭЛЕМЕНТ_НА_КОТОРЫЙ_ХОТИМ_ПОЛУЧИТЬ_КЛИК',
},
progress: {
current: 1,
total: 12,
},
},
},
{
id: "ID_ВТОРОГО_ШАГА",
type: StepTypes.INPUT,
text: "текст, который нужно ввести",
views: {
pointer: {
type: PointerType.DOM,
elementId: 'ЭЛЕМЕНТ_НА_КОТОРЫЙ_ХОТИМ_ПОЛУЧИТЬ_ВВОД',
},
progress: {
current: 2,
total: 12,
},
},
}
// Еще 10 шагов
]
} as const;
type StepIDS = typeof OUR_FIRST_ONBOARDING.steps[number]['id'];
interface Event {
type: string,
eventId: StepIDS
}
function emitEvent(id: Event) {
}
ну или вариант с несколькими конфигами
interface Event<T extends string> {
type: string,
eventId: T
}
function emitEvent<CompanyStepIds extends string>(id: Event<CompanyStepIds>) {}
emitEvent<StepIDS>({
type: '1',
eventId: 'ID_ПЕРВОГО_ШАГА'
})
Привет, спасибо за комментарий. Валидное замечание. Когда мы делали первую версию, легче было сделать так, как ты написал, но мы пошли немного дальше и теперь нужно описывать только интерфейс класса и все.
А дальше используешь его так:
// Конфиг
const OUR_FIRST_ONBOARDING = {
id: "НАШ_ПЕРВЫЙ_ОНБОРДИНГ",
steps: [
new StepClick{
id: "ID_ПЕРВОГО_ШАГА",
}
]
}
// Вызов события в основном приложении
OUR_FIRST_ONBOARDING.steps[0].emitEvent()
Да, спасибо, такой способ скорее всего удобнее.
Единственное мне кажется в конфигах должна лежать чисто инфа для конфигурирования, а всякие вызовы лучше делать в сервисах, но это уже вкусовщина.
я бы сделал как-то так, но возможно вы так пробовали и вам не зашло
class StepEvent {
constructor(public id: string) {}
emit() {}
}
class StepClick extends StepEvent {}
///
interface ConfigEvent {
type: 'click',
id: string,
}
interface Config {
id: string,
steps: ConfigEvent[]
}
///
const OUR_FIRST_ONBOARDING: Config = {
id: "НАШ_ПЕРВЫЙ_ОНБОРДИНГ",
steps: [
{
type: 'click',
id: 'ID_ПЕРВОГО_ШАГА',
}
]
}
class Service {
private events: StepEvent[]
constructor (private config: Config) {
this.events = config.steps.map(this.mapConfigEventToStepEvent)
}
private mapConfigEventToStepEvent(configEvent: ConfigEvent) {
return new StepClick(configEvent.id);
}
emitEvent(stepNum: number) {
const event = this.events[stepNum];
if (!event) {
return;
}
event.emit();
}
}
const service = new Service(OUR_FIRST_ONBOARDING);
service.emitEvent(0);
В первой версии у нас так и было, но здесь будут проблемы:
mapConfigEventToStepEvent
будет бесконечно расширяемой мапой из-за того что у нас будут появляться новый шаги. Метод приватный и нужно будет писать какие-то интеграционные тесты, чтобы проверить правильность работы класса. Из-за раздуваемости этой мапы, тесты будут очень хрупкие и и них будет увесистая фаза ArrangeСоздавая сразу инстанс класса для шага в конфиге мы можем спрятать свойства необходимые для этого класса внутрь
const OUR_FIRST_ONBOARDING = {
id: "НАШ_ПЕРВЫЙ_ОНБОРДИНГ",
steps: [
{
id: "ID_ПЕРВОГО_ШАГА",
// Этот параметр спрячется в классе
type: StepTypes.CLICK,
}
]
}
const OUR_FIRST_ONBOARDING = {
id: "НАШ_ПЕРВЫЙ_ОНБОРДИНГ",
steps: [
new StepClick{
id: "ID_ПЕРВОГО_ШАГА",
}
]
}
const EventTypes = {
click: 'click',
dbClick: 'dbClick',
} as const;
type EventType = keyof typeof EventTypes;
interface ConfigEvent {
type: EventType,
id: string,
}
interface Config {
id: string,
steps: ConfigEvent[]
}
///
const OUR_FIRST_ONBOARDING: Config = {
id: "НАШ_ПЕРВЫЙ_ОНБОРДИНГ",
steps: [
{
type: EventTypes.click,
id: 'ID_ПЕРВОГО_ШАГА',
}
]
}
class Service {
private events: StepEvent[]
/*
* можно вынести сюда Классы по типу
* и тут будет проверка на этапе компиляции
* а если бутет новый эвент, то эта строка стразу подсветится
*/
private eventClasses: Record<EventType, typeof StepEvent> = {
click: StepClick,
dbClick: StepClick,
}
constructor (private config: Config) {
this.events = config.steps.map(this.mapConfigEventToStepEvent)
}
// если будет eventClasses, то этот метод вообще не придется трогать
private mapConfigEventToStepEvent(configEvent: ConfigEvent) {
const EventClass = this.eventClasses[configEvent.type];
return new EventClass(configEvent.id);
}
emitEvent(stepNum: number) {
const event = this.events[stepNum];
if (!event) {
return;
}
event.emit();
}
}
В любом случае, я понимаю почему вы так сделали и спасибо за дискусию, просто не удержался вставить свои 5 копеек =(
Как мы написали, а потом переписали онбординг сервис