Share This
Связаться со мной
Крути в низ
Categories
//⚛ 5 продвинутых паттернов React-разработки

⚛ 5 продвинутых паттернов React-разработки

Обзор пяти современных передовых шаблонов разработки на React с их достоинствами и недостатками, а также примерами кода. Обсудить

5 prodvinutyh patternov react razrabotki 9772229 - ⚛ 5 продвинутых паттернов React-разработки

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

Идеальный библиотечный компонент React:

  • Предоставляет простой и понятный API;
  • Имеет несколько модификаций и вариантов использования, легко настраивается при необходимости;
  • Позволяет расширять и тонко контролировать свое поведение (для сложных ситуаций).

В поисках этого Идеального компонента сообщество React разработало несколько классных паттернов, которые вы должны взять на вооружение. Все они так или иначе позволяют разработчику вмешиваться в работу компонента и настраивать или модифицировать его под свои нужды.

Для удобства мы разберем все паттерны на одном примере и по одному плану. В качестве компонента будет выступать простой счетчик:

5 prodvinutyh patternov react razrabotki 1edf366 - ⚛ 5 продвинутых паттернов React-разработки

Компонент Counter будет реализован пятью разными способами

Для каждого шаблона будет небольшое введение, реальный вариант использования (со ссылкой на GitHub, где вы найдете и примеры реализации) и разбор плюсов и минусов. Затем подведем небольшой итог и выставим оценки по двум критериям:

  • Инверсия контроля (управления) – уровень гибкости и контроля, который ваш компонент предоставляет пользователям (другим разработчикам).
  • Сложность реализации и использования – для вас и других пользователей.

Весь исходный код доступен на GitHub: https://github.com/alex83130/advanced-react-patterns.

Также посмотрим, какие публичные библиотеки React уже используют тот или иной паттерн.

Паттерн #1. Составные компоненты

Этот шаблон разработки позволяет создавать понятные декларативные компоненты без многоуровневого пробрасывания пропсов. Его основное достоинство – разделение ответственности между несколькими элементами. Составные компоненты проще настраивать, и API у них максимально простой.

Пример использования

Github: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/compound-component

compound-component.js

         import React from "react"; import { Counter } from "./Counter";  function Usage() {   const handleChangeCounter = (count) => {     console.log("count", count);   };    return (     <Counter onChange={handleChangeCounter}>       <Counter.Decrement icon="minus" />       <Counter.Label>Counter</Counter.Label>       <Counter.Count max={10} />       <Counter.Increment icon="plus" />     </Counter>   ); }  export { Usage };     

Плюсы

Уменьшается сложность API

Больше нет необходимости передавать все параметры в один гигантский родительский компонент и затем пробрасывать их до дочерних элементов интерфейса. Теперь каждое свойство сразу прикрепляется к своему подкомпоненту – это выглядит проще и логичнее.

5 prodvinutyh patternov react razrabotki 24fd252 - ⚛ 5 продвинутых паттернов React-разработки

Гибкая структура разметки

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

5 prodvinutyh patternov react razrabotki 964b634 - ⚛ 5 продвинутых паттернов React-разработки

Разделение ответственности

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

5 prodvinutyh patternov react razrabotki f0058a2 - ⚛ 5 продвинутых паттернов React-разработки

Минусы

Слишком большая гибкость пользовательского интерфейса

Гибкость – это не всегда хорошо. Без должного контроля, она может привести к изменению интерфейса или даже поломке компонента. Например, ничто не мешает пользователю добавить дополнительный элемент или, наоборот, забыть что-то важное (подкомпонент или параметр).

Уровень гибкости, который вы готовы предоставить пользователю, зависит от многих аспектов. Иногда большая свобода не требуется.

5 prodvinutyh patternov react razrabotki f4e71b5 - ⚛ 5 продвинутых паттернов React-разработки

Громоздкая разметка

Очевидно, что количество строк разметки существенно увеличивается, ведь каждый элемент представлен отдельным компонентом, а не спрятан внутри родителя. Особенно это чувствуется при использовании линтеров (ESLint) или форматировщиков кода (Prettier).

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

5 prodvinutyh patternov react razrabotki a124ef8 - ⚛ 5 продвинутых паттернов React-разработки

Оценки

  • Инверсия управления: 1 из 4
  • Сложность реализации: 1 из 4

Публичные библиотеки, использующие этот паттерн

  • React Bootstrap
  • Reach UI

Паттерн #2. Управление свойствами

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

Пример использования

GitHub: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/control-props

control-props.js

         import React, { useState } from "react"; import { Counter } from "./Counter";  function Usage() {   const [count, setCount] = useState(0);    const handleChangeCounter = (newCount) => {     setCount(newCount);   };   return (     <Counter value={count} onChange={handleChangeCounter}>       <Counter.Decrement icon={"minus"} />       <Counter.Label>Counter</Counter.Label>       <Counter.Count max={10} />       <Counter.Increment icon={"plus"} />     </Counter>   ); }  export { Usage };     

Плюсы

Больше контроля

Пользователь полностью контролирует состояние компонента и напрямую влияет на его поведение.

5 prodvinutyh patternov react razrabotki 58c8c0d - ⚛ 5 продвинутых паттернов React-разработки

Минусы

Сложность реализации

Управляемый компонент нельзя просто подключить в одном месте и забыть. Требуется написать больше кода (JSX-разметка, useState и обработчик handleChange)

5 prodvinutyh patternov react razrabotki 06444c0 - ⚛ 5 продвинутых паттернов React-разработки

Оценки

  • Инверсия управления: 2 из 4
  • Сложность реализации: 1 из 4

Публичные библиотеки, использующие этот шаблон

  • Material UI

Паттерн #3. Кастомные хуки

Мы можем пойти еще дальше в инверсии управления и перенести основную логику компонента в кастомный хук, который доступен пользователю и предоставляет несколько внутренних логик (состояния, обработчики). Таким образом, пользователь может лучше контролировать ваш компонент.

Пример использования

GitHub: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/custom-hooks

custom-hooks.js

         import React from "react"; import { Counter } from "./Counter"; import { useCounter } from "./useCounter";  function Usage() {   const { count, handleIncrement, handleDecrement } = useCounter(0);   const MAX_COUNT = 10;    const handleClickIncrement = () => {     // ... пользовательская логика     if (count < MAX_COUNT) {       handleIncrement();     }   };    return (     <>       <Counter value={count}>         <Counter.Decrement           icon={"minus"}           onClick={handleDecrement}           disabled={count === 0}         />         <Counter.Label>Counter</Counter.Label>         <Counter.Count />         <Counter.Increment           icon={"plus"}           onClick={handleClickIncrement}           disabled={count === MAX_COUNT}         />       </Counter>       <button onClick={handleClickIncrement} disabled={count === MAX_COUNT}>         Custom increment btn 1       </button>     </>   ); }  export { Usage };     

Плюсы

Больше контроля

Пользователь может добавить свою собственную логику между вашим хуком и элементом JSX, что позволяет изменить дефолтное поведение компонента.

5 prodvinutyh patternov react razrabotki cd44624 - ⚛ 5 продвинутых паттернов React-разработки

Минусы

Сложность реализации

Логическая часть компонента полностью отделена от рендеринга, а значит пользователю придется связать их самостоятельно. Чтобы реализовать все правильно, разработчик должен хорошо понимать, как работает система в целом.

5 prodvinutyh patternov react razrabotki f37efcb - ⚛ 5 продвинутых паттернов React-разработки

Оценки

  • Инверсия управления: 2 из 4
  • Сложность реализации: 2 из 4

Публичные библиотеки, использующие этот шаблон

  • React table
  • React hook form

Паттерн #4. Геттер пропсов

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

Замаскировать эту сложность пытается шаблон Props Getter. Компонент предоставляет геттеры, которые возвращают список пропсов для связи с определенным элементом JSX-разметки. Теперь пользователь не должен указывать атрибуты самостоятельно, достаточно просто передать полученный список.

При этом остается возможность переопределить необходимые свойства.

Пример использования

GitHub: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/props-getters

props-getters.js

         import React from "react"; import { Counter } from "./Counter"; import { useCounter } from "./useCounter";  const MAX_COUNT = 10;  function Usage() {   const {     count,     getCounterProps,     getIncrementProps,     getDecrementProps   } = useCounter({     initial: 0,     max: MAX_COUNT   });    const handleBtn1Clicked = () => {     console.log("btn 1 clicked");   };    return (     <>       <Counter {...getCounterProps()}>         <Counter.Decrement icon={"minus"} {...getDecrementProps()} />         <Counter.Label>Counter</Counter.Label>         <Counter.Count />         <Counter.Increment icon={"plus"} {...getIncrementProps()} />       </Counter>       <button {...getIncrementProps({ onClick: handleBtn1Clicked })}>         Custom increment btn 1       </button>       <button {...getIncrementProps({ disabled: count > MAX_COUNT - 2 })}>         Custom increment btn 2       </button>     </>   ); }  export { Usage };     

Плюсы

Простота использования

Интеграция компонента в код становится проще, пользователю нужно только подключить правильный геттер к правильному элементу JSX. Дополнительная сложность от него скрыта.

5 prodvinutyh patternov react razrabotki ee1d643 - ⚛ 5 продвинутых паттернов React-разработки

Гибкость

Пользователь компонента по-прежнему имеет возможность перегружать пропсы при необходимости.

5 prodvinutyh patternov react razrabotki 202444a - ⚛ 5 продвинутых паттернов React-разработки

Минусы

Непрозрачность

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

Оценки

  • Инверсия управления: 3 из 4
  • Сложность интеграции: 3 из 4

Публичные библиотеки, использующие этот шаблон

  • React table
  • Downshift

Паттерн #5. Редуктор состояния

Самый продвинутый с точки зрения инверсии контроля паттерн, который дает пользователю расширенные возможности изменить внутреннюю работу вашего компонента.

Код похож на паттерн с кастомным хуком, но здесь пользователь еще должен сам написать редуктор, который будет обрабатывать все внутренние действия компонента, и передать его хуку.

Пример использования

GitHub: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/state-reducer

state-reducer.js

         import React from "react"; import { Counter } from "./Counter"; import { useCounter } from "./useCounter";  const MAX_COUNT = 10; function Usage() {   const reducer = (state, action) => {     switch (action.type) {       case "decrement":         return {           count: Math.max(0, state.count - 2) //The decrement delta was changed for 2 (Default is 1)         };       default:         return useCounter.reducer(state, action);     }   };    const { count, handleDecrement, handleIncrement } = useCounter(     { initial: 0, max: 10 },     reducer   );    return (     <>       <Counter value={count}>         <Counter.Decrement icon={"minus"} onClick={handleDecrement} />         <Counter.Label>Counter</Counter.Label>         <Counter.Count />         <Counter.Increment icon={"plus"} onClick={handleIncrement} />       </Counter>       <button onClick={handleIncrement} disabled={count === MAX_COUNT}>         Custom increment btn 1       </button>     </>   ); }  export { Usage };     

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

Плюсы

Больше контроля

Использование редукторов состояний – лучший способ передать управление компонентом пользователю. Оно идеально подходит для сложных случаев, когда требуется самая тонкая настройка. Все действия компонента доступны извне и могут быть переопределены.

5 prodvinutyh patternov react razrabotki 667f6f5 - ⚛ 5 продвинутых паттернов React-разработки

Минусы

Сложность реализации

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

Непрозрачность

Так как любое действие редуктора может быть изменено, от пользователя требуется хорошее понимание внутренней логики компонента.

Оценки

  • Инверсия управления: 4 из 4
  • Сложность интеграции: 4 из 4

Публичные библиотеки, использующие этот шаблон

  • Downshift

*** Мы разобрали пять продвинутых React-паттернов, использующих концепцию инверсии управления. Они позволяют создавать компоненты с необходимым уровнем гибкости и адаптируемости.

Нельзя забывать о том, что «с большой силой приходит большая ответственность, Питер». Чем больший контроль вы передаете пользователю, тем сложнее работать с вашим компонентом – его уже не получится просто подключить и сразу же начать пользоваться. Вы, как разработчик, должны самостоятельно определить, какой шаблон отвечает вашим задачам больше всего.

Вам в помощь небольшая диаграмма, классифицирующая все рассмотренные паттерны по сложности интеграции и инверсии управления.

5 prodvinutyh patternov react razrabotki 497a113 - ⚛ 5 продвинутых паттернов React-разработки

  • 2 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close