Этот пост был впервые опубликован в моем блоге: Создание абстракции для сообщений интернационализации React.
Я натолкнулся на функцию, которую хотел создать, и часть ее включала рендеринг интернационализированного текста на основе типа данных из API. Этот API может возвращать три типа: common
, password
или biometry
. И мы используем его для создания нашего EntryInfo
компонента.
Для типа common
ответ API выглядит так:
{
type: 'common',
ownerName: 'TK',
password: null
}
Тип - common
, password
- null
, а ownerName
представлен в виде строки.
Для типа password
:
{
type: 'password',
ownerName: null,
password: 'lololol'
}
Тип - password
, ownerName
- null
, но password
присутствует в виде строки.
А для типа biometry
:
{
type: 'biometry',
ownerName: null,
password: null
}
Тип - biometry
, без ownerName
и password
.
Это три возможные полезные данные, которые мы получаем от API. И мне нужно было визуализировать интернационализированный текст на основе этих данных.
Логика построения текста сообщения на основе типа и других значений:
- когда
type
равно'Common'
, он отображает'Owner {ownerName} will be there'
- когда
type
равно'Password'
, он отображает'Password: {password}'
- когда
type
равно'Biometry'
, он отображает'Type: biometry'
- когда
type
равноnull
, он отображает'Call the owner'
Итак, первое, что я сделал, - это построил определения сообщений:
import { defineMessages } from 'react-intl';
export const messages = defineMessages({ common: { id: 'app.containers.entryInfo.owner', defaultMessage: 'Owner {ownerName} will be there', }, password: { id: 'app.containers.entryInfo.password', defaultMessage: 'Password: {password}', }, biometry: { id: 'app.containers.entryInfo.biometry', defaultMessage: 'Type: biometry', }, defaultMessage: { id: 'app.containers.entryInfo.defaultMessage', defaultMessage: 'Call the owner', }, };
Компонент EntryInfo
будет выглядеть так:
const EntryInfo = ({ type, password, ownerName, intl }) => { let entryInfo;
if (type === 'common') { entryInfo = intl.format(messages.common, { ownerName }); } else if (type === 'password') { entryInfo = intl.format(messages.password, { password }); } else if (type === 'biometry') { entryInfo = intl.format(messages.biometry); } else { entryInfo = intl.format(messages.defaultMessage); }
return <p>{entryInfo}</p> };
export default injectIntl(EntryInfo);
Чтобы следовать определенной логике, я просто добавил if-elseif-else
, чтобы отобразить соответствующее сообщение с помощью функции intl.format
. Это просто, функция intl.format
получает соответствующее сообщение, возвращает текст информации о записи и передает его компоненту для рендеринга.
Но я мог бы выделить его во вспомогательной функции getEntryInfo
, чтобы отделить логику от компонента пользовательского интерфейса. Я также мог экспортировать его для модульного тестирования без особых сложностей.
Я также передаю объект intl
этой новой функции, чтобы вернуть правильную строку.
const getEntryInfo = ({ type, password, ownerName, intl }) => { if (type === 'common') { return intl.format(messages.common, { ownerName }); } else if (type === 'password') { return intl.format(messages.password, { password }); } else if (type === 'biometry') { return intl.format(messages.biometry); } else { return intl.format(messages.defaultMessage); } };
const EntryInfo = ({ type, password, ownerName, intl }) => { const entryInfo = getEntryInfo({ type, password, ownerName, intl });
return <p>{entryInfo}</p> };
export default injectIntl(EntryInfo);
Эта логика больше похожа на случай переключения, когда сравнивается только значение type
. Итак, небольшой рефакторинг в getEntryInfo
:
const getEntryInfo = ({ type, password, ownerName, intl }) => {
switch (type) {
case 'Common':
return intl.format(messages.common, { ownerName });
case 'Password':
return intl.format(messages.password, { password });
case 'Biometry':
return intl.format(messages.biometry);
default:
return intl.format(messages.defaultMessage);
}
};
Тип жестко запрограммирован, поэтому мы также можем реорганизовать эти константы с помощью перечисления:
const ENTRY_INFO_TYPES = Object.freeze({ COMMON: 'Common', PASSWORD: 'Password', BIOMETRY: 'Biometry', });
const getEntryInfo = ({ type, password, ownerName, intl }) => { switch (type) { case ENTRY_INFO_TYPES.COMMON: return intl.format(messages.common, { ownerName }); case ENTRY_INFO_TYPES.PASSWORD: return intl.format(messages.password, { password }); case ENTRY_INFO_TYPES.BIOMETRY: return intl.format(messages.biometry); default: return intl.format(messages.defaultMessage); } };
Теперь все готово.
Думая о cohesion
, я подумал, что функция getEntryInfo
слишком много знает о том, как компонент отображает текстовое сообщение (с помощью intl
).
Одна идея - подумать об единственной ответственности каждой функции.
Итак, для функции getEntryInfo
мы можем удалить параметр intl
как зависимость и построить объект сообщения вместо возврата строки.
const getEntryInfoMessage = ({ type, password, ownerName }) => {
switch (type) {
case ENTRY_INFO_TYPES.COMMON:
return { message: messages.common, values: { ownerName } };
case ENTRY_INFO_TYPES.PASSWORD:
return { message: messages.password, values: { password } };
case ENTRY_INFO_TYPES.BIOMETRY:
return { message: messages.biometry, values: {} };
default:
return { message: messages.defaultMessage, values: {} };
}
};
И используйте это в компоненте:
const EntryInfo = ({ type, password, ownerName, intl }) => { const entryInfoMessage = getEntryInfoMessage({ type, password, ownerName });
return <p> {intl.format( entryInfoMessage.message, entryInfoMessage.values )} </p> }
В качестве рефакторинга компонента мы можем деструктурировать объект сообщения:
const EntryInfo = ({ type, password, ownerName, intl }) => { const { message, values } = getEntryInfoMessage({ type, password, ownerName });
return <p>{intl.format(message, values)}</p>; }
Он более читабельный и менее подробный.
Для объекта сообщения мы можем создать простую функцию для обработки создания объекта сообщения:
const buildMessageObject = (message, values = {}) => ({ message, values, });
const getEntryInfoMessage = ({ type, password, ownerName }) => { switch (type) { case ENTRY_INFO_TYPES.COMMON: return buildMessageObject(messages.common, { ownerName }); case ENTRY_INFO_TYPES.PASSWORD: return buildMessageObject(messages.password, { password }); case ENTRY_INFO_TYPES.BIOMETRY: return buildMessageObject(messages.biometry); default: return buildMessageObject(messages.defaultMessage); } };
Взгляните на аргумент values = {}
. Мы добавляем этот пустой объект в качестве значения по умолчанию, чтобы ничего не передавать в случаях biometry
и default
.
Без зависимости intl
легче использовать и тестировать функцию. Он больше полагается только на данные, а не на зависимости.
Заключительный компонент
Полный компонент со всей отделенной логикой более сплочен. Каждая часть несет свою ответственность, и это помогает уменьшить сцепление.
const messages = defineMessages({ common: { id: 'app.containers.entryInfo.owner', defaultMessage: 'Owner {ownerName} will be there', }, password: { id: 'app.containers.entryInfo.password', defaultMessage: 'Password: {password}', }, biometry: { id: 'app.containers.entryInfo.biometry', defaultMessage: 'Type: biometry', }, defaultMessage: { id: 'app.containers.entryInfo.default', defaultMessage: 'Call the owner', }, }
const ENTRY_INFO_TYPES = Object.freeze({ COMMON: 'Common', PASSWORD: 'Password', BIOMETRY: 'Biometry', });
const buildMessageObject = (message, values = {}) => ({ message, values, });
const getEntryInfoMessage = ({ type, password, ownerName }) => { switch (type) { case ENTRY_INFO_TYPES.COMMON: return buildMessageObject(messages.common, { ownerName }); case ENTRY_INFO_TYPES.PASSWORD: return buildMessageObject(messages.password, { password }); case ENTRY_INFO_TYPES.BIOMETRY: return buildMessageObject(messages.biometry); default: return buildMessageObject(messages.defaultMessage); } };
const EntryInfo = ({ type, password, ownerName, intl }) => { const { message, values } = getEntryInfoMessage({ type, password, ownerName });
return <p>{intl.format(message, values)}</p>; }
export default injectIntl(EntryInfo);
Ресурсы
- Начинающий JavaScript
- Изучите ES6 - JavaScript
- Начинающий React
- Один месяц учебного курса по Javascript
- Полный стек Advanced React
- Дорога к изучению React
- Основы JavaScript перед изучением React
- Повторное знакомство с React: V16 и не только
- Расширенные шаблоны реакции с хуками
- Реагировать в паттернах
- Высокое сцепление и низкое сцепление
Надеюсь, вам понравился этот контент. Поддержите мою работу над Ko-Fi