Когда вам нужно взаимодействовать между страницами, у вас есть несколько вариантов:
- Использовать возможности событий вашего фреймворка
- Иметь глобальное хранилище и поместить что-то туда
- Использовать строку запроса для добавления информации
Все эти варианты имеют свои недостатки и могут не всегда работать из-за того, как маршрутизация и компоненты обрабатываются в вашем фреймворке. У вас также есть вопросы «следует ли мне изменять данные до или после изменения маршрутов?» и «увидит ли мое изменение данных мой компонент?».
У вас есть виджет ContactsCrud
, который содержит строки элементов Contact
. У вас есть компонент ContactPage
, который отображает информацию о контакте (которая включает историю звонков). Находясь на ContactsCrud
, при нажатии на номер телефона контакта вы хотите перейти на соответствующую ContactPage
и выполнить там код, который начинает новый звонок (и отображает соответствующий UI).
Как это сделать… Вы не можете использовать события, поскольку компонент еще не отрисован и, следовательно, не может реагировать на них, а если бы вы слушали событие при монтировании, то было бы слишком поздно. Вы можете использовать глобальное хранилище, но тогда вам придется писать шаблон каждый раз, когда вам понадобится что-то подобное. То же самое касается строки запроса, но это еще сложнее.
Мое решение? Передача сообщений / паттерн проектирования команд.
У нас нет доступа к (G)ADT, поэтому сначала мы поможем typescript с определениями наших сообщений:
/**
* Here we define both the messages' names
* and their data payload. Note that we use
* `{}` if we don't need data, just so we
* can use equality comparisons in our implementations
*/
export interface MessagesPayloadMap {
StartNewCall: {
contactId: API.Id;
};
Test: {};
}
/**
* We can use types that extend from this
* to get god-tier autocompletion
*/
export type MessageType = keyof MessagesPaylodMap;
/**
* Just a shorthand to get the payload for the
* given message type
*/
export type MessagePayload<M extends MessageType> = MessagesPayloadMap[M];
/**
* How our messages actually look like
*/
export interface Message<M extends MessageType> {
name: M;
data: MessagePayload<M>;
};
/**
* The type of our enum-style object that contains
* all the message factories
*/
export type MessageFactoriesEnum = {
[M in MessageType]: (payload?: MessagePayload<M>): Message<M>;
}
/**
* What we get when querying messages
*/
export interface MessageQuery<M extends MessageType> {
message: Message<M>;
/**
* When querying, calling this function will
* remove the message from the system
*/
markAsCompleted: () => void;
}
/**
* Type of the callbacks that process messages
*/
export type MessageProcessor<M extends MessageType> = (message: Message<M>, markAsCompleted: () => void) => void;
Помня об этих определениях типов, мы можем перейти к сути дела:
export const Messages: MessageFactoriesEnum = {
StartNewCall: (data: MessagePayload<"StartNewCall">) => ({
name: "StartNewCall",
data,
})
Test: () => ({
name: "Test",
data: {},
}),
};
export interface WorkQueue<M extends MessageType> {
getQuery(): MessageQuery<M>[];
/**
* Processor might not be called (if the queue is empty)
*/
query(messageProcessor: MessageProcessor<M>): void;
/**
* Processor might not be called (if the queue is empty)
*/
queryLast(messageProcessor: MessageProcessor<M>): void;
/**
* Processor might not be called (if the queue is empty)
*/
queryFirst(messageProcessor: MessageProcessor<M>): void;
};
export interface MessagePassing {
emit<M extends MessageType>(messageType: M, payload: MessagePayload<M>): void;
dispatch<M extends MessageType>(message: Message<M>): void;
on<M extends MessageType>(messageType: M, messageProcessor: MessageProcessor<M>): void;
off<M extends MessageType>(messageType: M, messageProcessor: MessageProcessor<M>): void;
once<M extends MessageType>(messageType: M, messageProcessor: MessageProcessor<M>): void;
queryLast<M extends MessageType>(messageType: M, messageProcessor: MessageProcessor<M>): void;
queryFirst<M extends MessageType>(messageType: M, messageProcessor: MessageProcessor<M>): void;
query<M extends MessageType>(messageType: M, messageProcessor: MessageProcessor<M>): void;
}
И с помощью этого мы можем избавиться от головной боли, связанной с вызовами:
- Перед навигацией просто вызовите
mp.dispatch(Messages.StartNewCall({ contactId: 42 }))
. - При первом рендере вашей
ContactPage
, в любом подкомпоненте, где это необходимо, просто вызовитеmp.queryLast(callback)
.
Бонусы этого подхода следующие:
- Вы можете иметь несколько экземпляров, запрашивающих сообщения (пока только один отмечает их как завершенные).
- Запросы сообщений «независимы» от фактических сообщений в очереди (т.е. удаление сообщений из очереди не приведет к их удалению из запроса).
- Вы можете отправлять сообщения куда угодно, если у вас есть данные для этого.
- Он никогда не понадобится на сервере, поэтому вы можете сделать его только клиентским плагином для вашего фреймворка.