[JavaScript, ReactJS] React. Не в глубь, а в ширь. Композиция против реальности
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Давайте рассмотрим искусственный пример кода, который, как и в жизни, постепенно будет расширяться и усложняться, а наша задача, глядя на это всё, понять: не пора ли рефакторить. План наших действий: задача – решение – анализ – рефакторинг. Приступим.Задача: в проект нужны тултипы. Сказано – сделано.
interface OwnProps {
hint: string
}
export const Tooltip: FC<OwnProps> = ({ hint, children }) => {
// допустим, в зависимости от кол-ва символов и пространства на экране
// производится позиционирование
const [config, setConfig] = useState(null)
const ref = useRef(null)
useLayoutEffect(() => {
// реализация алгоритма позиционирования
// ...
setConfig(someConfig)
}, [hint])
return (
<div ref={ref}>
{children}
<TooltipComponent config={config} hint={hint} />
</div>
)
Спустя какое-то время в проекте должен появиться ещё один тултип, он красивее и принимает обработчик клика. Самое простое и кратчайшее решение – изменить имеющийся компонент Tooltip.
interface TooltipProps {
hint: string
onClick?: () => void
}
export const Tooltip: FC<TooltipProps> = ({ hint, children, onClick }) => {
// допустим, в зависимости от кол-ва символов и пространства на экране
// производится позиционирование
const [config, setConfig] = useState(null)
const ref = useRef(null)
useLayoutEffect(() => {
// реализация алгоритма позиционирования
// ...
setConfig(someConfig)
}, [hint])
// А ВОТ И НОВЫЙ ВАРИАНТ ИСПОЛЬЗОВАНИЯ!!!
// в этом компоненте уже обязательно нужен onClick
if (onClick) {
return (
<div ref={ref}>
{children}
<AnotherTooltipComponent config={config} hint={hint} onClick={onClick} />
</div>
)
}
return (
<div ref={ref}>
{children}
<TooltipComponent config={config} hint={hint} />
</div>
)
}
Мы модифицировали старый компонент, добавили инструкцию if и всё заработало. Единственное, что несколько смущает на данном этапе, это то, что из интерфейса TooltipProps совсем не очевидно, что обработчик onClick на самом деле не просто опциональное свойство, а ещё и определитель: какой вариант тултипа нужно вернуть. В общем, может и не очевидно, а может и очевидно, ясно одно: Done is better than perfect.
И вот нас снова просят добавить новый тултип – DiscountTooltipComponent, который тоже обязательным свойством принимает обработчик onClick. Чтобы отличать два компонента DiscountTooltipComponent от AnotherTooltipComponent мы используем дополнительное свойство type.
interface TooltipProps {
hint: string
type?: 'another' | 'discount'
onClick?: () => void
}
export const Tooltip: FC<TooltipProps> = ({ type, hint, children, onClick }) => {
// допустим, в зависимости от кол-ва символов и пространства на экране
// производится позиционирование
const [config, setConfig] = useState(null)
const ref = useRef(null)
useLayoutEffect(() => {
// реализация алгоритма позиционирования
// ...
setConfig(someConfig)
}, [hint])
// А ВОТ И НОВЫЙ ВАРИАНТ ИСПОЛЬЗОВАНИЯ!!!
// в этом компоненте уже обязательно нужен onClick
if (type && onClick) {
return (
<div ref={ref}>
{children}
{type === 'another' ? (
<AnotherTooltipComponent config={config} hint={hint} onClick={onClick} />
) : (
<DiscountTooltipComponent config={config} hint={hint} onClick={onClick} />
}
</div>
)
}
return (
<div ref={ref}>
{children}
<TooltipComponent config={config} hint={hint} />
</div>
)
}
Условие в инструкции if усложнилось, внутри появился тернарный оператор, само собой код стало сложнее читать. И вот когда условия становятся сложнее и специфичнее, стоит это дело проанализировать. Начнём сверху, с интерфейса TooltipProps. Глядя на него, совсем не очевидно, что поля type и onClick связаны между собой. Следовательно, не очевидны и варианты использования компонента Tooltip. Мы можем указать type = "another", но не передать onClick, и тогда typescript не выдаст ошибки.Самое время обратиться к принципу разделения интерфейсов (Interface Segregation Principle), который на уровне компонентов называется принципом совместного повторного использования. Он гласит:
Не вынуждайте пользователей компонента зависеть от того, чего им не требуется.
Чтобы проблема стала видна отчётливее, представим, что прошло ещё немного времени.
Аналитики просят залогировать нажатие на DiscountTooltipComponent.
interface TooltipProps {
hint: string
type?: 'another' | 'discount'
onClick?: () => void
}
export const Tooltip: FC<TooltipProps> = ({ type, hint, children, onClick }) => {
// допустим, в зависимости от кол-ва символов и пространства на экране
// производится позиционирование
const [config, setConfig] = useState(null)
const ref = useRef(null)
useLayoutEffect(() => {
// реализация алгоритма позиционирования
// ...
setConfig(someConfig)
}, [hint])
// ЗДЕСЬ МЫ БУДЕМ ЛОГИРОВАТЬ
const handleClick = () => {
if (type === 'discount') {
// произвести логирование
}
if (onClick) {
onClick()
}
}
// А ВОТ И НОВЫЙ ВАРИАНТ ИСПОЛЬЗОВАНИЯ!!!
// в этом компоненте уже обязательно нужен onClick
if (type) {
return (
<div ref={ref}>
{children}
{type === 'another' ? (
<AnotherTooltipComponent config={config} hint={hint} onClick={handleClick} />
) : (
<DiscountTooltipComponent config={config} hint={hint} onClick={handleClick} />
}
</div>
)
}
return (
<div ref={ref}>
{children}
<TooltipComponent config={config} hint={hint} />
</div>
)
}
Теперь все, кто использовал Tooltip в его первозданном виде, получили в нагрузку handleClick, который ими никак не используется, но ресурсы на него расходуются. А те, кто использовал компонент с type='another', получили не нужную обертку handleClick. Что, если мы разделим интерфейсы, например:
interface Tooltip {
hint: string
}
interface TooltipInteractive extends Tooltip {
onClick: () => void
}
Теперь выделим общую логику в компонент TooltipSettings:
interface TooltipSettingsProps {
hint: string
render: (config: any, hint: string) => JSX.Element
}
export const TooltipSettings: FC<TooltipSettingsProps> = ({ render }) => {
// допустим в зависимости от кол-ва символов и пространства на экране
// производится позиционирование
const [config, setConfig] = useState(null)
const ref = useRef(null)
useLayoutEffect(() => {
// реализация алгоритма позиционирования
// ...
setConfig(someConfig)
}, [hint])
return (
<div ref={ref}>
{children}
{render(config, hint)}
</div>
)
}
Реализуем интерфейс Tooltip:
export const Tooltip: FC<Tooltip> = ({ hint }) => (
<TooltipSettings hint={hint} render={(config, hint) => <TooltipComponent config={config} hint={hint} />} />
)
Реализуем интерфейс TooltipInteractive:
export const AnotherTooltip: FC<TooltipInteractive> = ({ hint, onClick }) => (
<TooltipSettings
hint={hint}
render={(config, hint) => <AnotherTooltipComponent onClick={onClick} config={config} hint={hint} />}
/>
)
В частности DiscountTooltipComponent:
export const DiscountTooltip: FC<TooltipInteractive> = ({ hint, onClick }) => {
const handleClick = () => {
// произвести логирование
// вызвать обработчик
onClick()
}
return (
<TooltipSettings
hint={hint}
render={(config, hint) => <DiscountTooltipComponent onClick={handleClick} config={config} hint={hint} />}
/>
)
}
Ничто так не усложняет понимание кода, как обилие ветвлений – в том числе инструкций if, – и специфичных условий. Чем быстрее мы стараемся реализовать задачу, тем больше компонентов мы помещаем "под одной крышей". Это помогает выиграть время на короткой дистанции, но вероятность того, что однажды этот код станет легче полностью переписать, чем расширить или изменить, неуклонно возрастает. Предугадать, как будет развиваться проект даже в ближайшие год-два, задача нетривиальная в нашем быстро меняющемся мире, а вот вовремя реагировать на изменения – задача вполне посильная.
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, Google Chrome, API, Расширения для браузеров, Реверс-инжиниринг] Как я доделал функции за Яндекс.Музыкой
- [JavaScript, Node.JS] Как работает Middleware в Express?
- [JavaScript, Fidonet, HTML, Карьера в IT-индустрии] X5 frontend meetup
- [JavaScript, Профессиональная литература] Книга «JavaScript с нуля»
- [Angular, ReactJS, TypeScript] Почему мы должны выбросить React и взяться за Angular (перевод)
- [Разработка мобильных приложений, ReactJS] Десятикратное улучшение производительности React-приложения (перевод)
- [Разработка веб-сайтов, JavaScript, Программирование, HTML] Webix Datatable. От простой таблицы к сложному приложению
- [Java, Kotlin] Разгоняем REACTOR
- [JavaScript, Node.JS, Криптовалюты] Как написать пассивный доход: Пишем качественного трейд бота на JS (часть 1)
- [Разработка веб-сайтов, JavaScript, HTML, Видеоконференцсвязь] Как мы интрегрировали Agora SDK в проект
Теги для поиска: #_javascript, #_reactjs, #_javascript, #_react, #_robert_martin (роберт мартин), #_kompozitsija_komponentov (композиция компонентов), #_blog_kompanii_sitimobil (
Блог компании Ситимобил
), #_javascript, #_reactjs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:58
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Давайте рассмотрим искусственный пример кода, который, как и в жизни, постепенно будет расширяться и усложняться, а наша задача, глядя на это всё, понять: не пора ли рефакторить. План наших действий: задача – решение – анализ – рефакторинг. Приступим.Задача: в проект нужны тултипы. Сказано – сделано. interface OwnProps {
hint: string } export const Tooltip: FC<OwnProps> = ({ hint, children }) => { // допустим, в зависимости от кол-ва символов и пространства на экране // производится позиционирование const [config, setConfig] = useState(null) const ref = useRef(null) useLayoutEffect(() => { // реализация алгоритма позиционирования // ... setConfig(someConfig) }, [hint]) return ( <div ref={ref}> {children} <TooltipComponent config={config} hint={hint} /> </div> ) interface TooltipProps {
hint: string onClick?: () => void } export const Tooltip: FC<TooltipProps> = ({ hint, children, onClick }) => { // допустим, в зависимости от кол-ва символов и пространства на экране // производится позиционирование const [config, setConfig] = useState(null) const ref = useRef(null) useLayoutEffect(() => { // реализация алгоритма позиционирования // ... setConfig(someConfig) }, [hint]) // А ВОТ И НОВЫЙ ВАРИАНТ ИСПОЛЬЗОВАНИЯ!!! // в этом компоненте уже обязательно нужен onClick if (onClick) { return ( <div ref={ref}> {children} <AnotherTooltipComponent config={config} hint={hint} onClick={onClick} /> </div> ) } return ( <div ref={ref}> {children} <TooltipComponent config={config} hint={hint} /> </div> ) } И вот нас снова просят добавить новый тултип – DiscountTooltipComponent, который тоже обязательным свойством принимает обработчик onClick. Чтобы отличать два компонента DiscountTooltipComponent от AnotherTooltipComponent мы используем дополнительное свойство type. interface TooltipProps {
hint: string type?: 'another' | 'discount' onClick?: () => void } export const Tooltip: FC<TooltipProps> = ({ type, hint, children, onClick }) => { // допустим, в зависимости от кол-ва символов и пространства на экране // производится позиционирование const [config, setConfig] = useState(null) const ref = useRef(null) useLayoutEffect(() => { // реализация алгоритма позиционирования // ... setConfig(someConfig) }, [hint]) // А ВОТ И НОВЫЙ ВАРИАНТ ИСПОЛЬЗОВАНИЯ!!! // в этом компоненте уже обязательно нужен onClick if (type && onClick) { return ( <div ref={ref}> {children} {type === 'another' ? ( <AnotherTooltipComponent config={config} hint={hint} onClick={onClick} /> ) : ( <DiscountTooltipComponent config={config} hint={hint} onClick={onClick} /> } </div> ) } return ( <div ref={ref}> {children} <TooltipComponent config={config} hint={hint} /> </div> ) } Не вынуждайте пользователей компонента зависеть от того, чего им не требуется.
Аналитики просят залогировать нажатие на DiscountTooltipComponent. interface TooltipProps {
hint: string type?: 'another' | 'discount' onClick?: () => void } export const Tooltip: FC<TooltipProps> = ({ type, hint, children, onClick }) => { // допустим, в зависимости от кол-ва символов и пространства на экране // производится позиционирование const [config, setConfig] = useState(null) const ref = useRef(null) useLayoutEffect(() => { // реализация алгоритма позиционирования // ... setConfig(someConfig) }, [hint]) // ЗДЕСЬ МЫ БУДЕМ ЛОГИРОВАТЬ const handleClick = () => { if (type === 'discount') { // произвести логирование } if (onClick) { onClick() } } // А ВОТ И НОВЫЙ ВАРИАНТ ИСПОЛЬЗОВАНИЯ!!! // в этом компоненте уже обязательно нужен onClick if (type) { return ( <div ref={ref}> {children} {type === 'another' ? ( <AnotherTooltipComponent config={config} hint={hint} onClick={handleClick} /> ) : ( <DiscountTooltipComponent config={config} hint={hint} onClick={handleClick} /> } </div> ) } return ( <div ref={ref}> {children} <TooltipComponent config={config} hint={hint} /> </div> ) } interface Tooltip {
hint: string } interface TooltipInteractive extends Tooltip { onClick: () => void } interface TooltipSettingsProps {
hint: string render: (config: any, hint: string) => JSX.Element } export const TooltipSettings: FC<TooltipSettingsProps> = ({ render }) => { // допустим в зависимости от кол-ва символов и пространства на экране // производится позиционирование const [config, setConfig] = useState(null) const ref = useRef(null) useLayoutEffect(() => { // реализация алгоритма позиционирования // ... setConfig(someConfig) }, [hint]) return ( <div ref={ref}> {children} {render(config, hint)} </div> ) } export const Tooltip: FC<Tooltip> = ({ hint }) => (
<TooltipSettings hint={hint} render={(config, hint) => <TooltipComponent config={config} hint={hint} />} /> ) export const AnotherTooltip: FC<TooltipInteractive> = ({ hint, onClick }) => (
<TooltipSettings hint={hint} render={(config, hint) => <AnotherTooltipComponent onClick={onClick} config={config} hint={hint} />} /> ) export const DiscountTooltip: FC<TooltipInteractive> = ({ hint, onClick }) => {
const handleClick = () => { // произвести логирование // вызвать обработчик onClick() } return ( <TooltipSettings hint={hint} render={(config, hint) => <DiscountTooltipComponent onClick={handleClick} config={config} hint={hint} />} /> ) } =========== Источник: habr.com =========== Похожие новости:
Блог компании Ситимобил ), #_javascript, #_reactjs |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:58
Часовой пояс: UTC + 5