От автора: React с JSX — фантастический инструмент для создания простых в использовании компонентов. Компоненты Typescript доставляют разработчикам абсолютное удовольствие интегрировать компоненты в приложения и исследовать API. В этой статье вы узнаете о трех менее известных API React, которые могут вывести ваши компоненты на новый уровень и помочь вам создавать еще более совершенные компоненты React.
Вы когда-нибудь использовали React.createElement напрямую? А как на счет React.cloneElement? React — это больше, чем просто преобразование JSX в HTML. Гораздо больше, и чтобы помочь вам повысить уровень ваших знаний о менее известных (но очень полезных) API, с которыми поставляется библиотека React, мы собираемся рассмотреть некоторые из них и некоторые варианты их использования, которые могут значительно улучшить интеграцию и полезность ваших компонентов.
В этой статье мы рассмотрим несколько полезных API React, которые не так широко известны, но чрезвычайно полезны для веб-разработчиков. Читатели должны иметь опыт работы с синтаксисом React и JSX, знание Typescript желательно, но не обязательно. Читатели познакомятся с тем, что им нужно знать, чтобы значительно улучшить компоненты React при их использовании в своих приложениях.
React.cloneElement
Большинство разработчиков, возможно, никогда не слышали о cloneElement и никогда не использовали его. Он был введен относительно недавно для замены устаревшей функции cloneWithProps. СloneElement клонирует элемент, а также позволяет объединять новые свойства с существующим элементом, изменяя или переопределяя их по своему усмотрению. Это открывает чрезвычайно мощные возможности для создания API для функциональных компонентов. Взгляните на его сигнатуру:
function cloneElement( element, props?, ...children)
Вот сокращенная версия Typescript:
function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement
Вы можете взять элемент, изменить его, даже переопределить его дочерние элементы, а затем вернуть его как новый элемент. Взгляните на следующий пример. Допустим, мы хотим создать компонент ссылок TabBar. Это может выглядеть примерно так.
export interface ITabbarProps { links: {title: string, url: string}[] } export default function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => <a key={i} href={e.url}>{e.title}</a> )} </> ) }
TabBar — это список ссылок, но нам нужен способ определения двух частей данных: заголовка ссылки и URL-адреса. Итак, нам нужна структура данных, передаваемая с этой информацией. Так что наш разработчик сделал бы этот компонент таким.
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
Это здорово, но что, если пользователь хочет отображать элементы button вместо элементов a? Что ж, мы могли бы добавить еще одно свойство, которое сообщает компоненту, какой тип нужно отображать.
Но вы можете видеть, как быстро код станет громоздким, и нам потребуется поддерживать все больше и больше свойств для обработки различных вариантов использования и граничных случаев для максимальной гибкости. Вот лучший способ — использовать React.cloneElement.
Мы начнем с изменения нашего интерфейса, чтобы он ссылался на тип ReactNode. Это общий тип, который охватывает все, что может визуализировать React, обычно элементы JSX, но также могут быть string и даже null. Это полезно для обозначения того, что вы хотите принимать в качестве встроенных аргументов: компоненты React или JSX.
export interface ITabbarProps { links: ReactNode[] }
Теперь мы просим пользователя предоставить нам несколько элементов React, и мы отрендерим их так, как захотим.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
Это совершенно верно и будет отображать наши элементы. Но мы забываем пару вещей. Во-первых, key! Мы хотим добавить ключи, чтобы React мог эффективно отображать наши списки. Мы также хотим изменить наши элементы, чтобы сделать необходимые преобразования, для соответствия стилю, например className, и так далее.
Мы можем сделать это с помощью React.cloneElement и другой функции React.isValidElement для проверки соответствия аргумента тому, что мы ожидаем!
React.isValidElement
Эта функция возвращает true, если элемент является допустимым элементом React и React может его отобразить. Вот пример изменения элементов из предыдущего примера.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
Здесь мы добавляем свойство key к каждому элементу, который мы передаем, и одновременно выделяем каждую ссылку жирным шрифтом! Теперь мы можем принимать произвольные элементы React в качестве свойств:
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
Мы можем переопределить любые свойства, установленные для элемента, и легко принять различные типы элементов, что делает наш компонент более гибким и простым в использовании.
Преимущество здесь в том, что если бы мы хотели установить собственный обработчик onClick для нашей кнопки, мы могли бы это сделать. Принятие элементов React в качестве аргументов — мощный способ придать гибкость дизайну вашего компонента.
Функция сеттера useState
Используйте хуки! Хук useState является чрезвычайно полезным и фантастическим API для быстрого создания состояния ваших компонентов следующим образом:
const [myValue, setMyValue] = useState()
Во время выполнения JavaScript возможны некоторые сбои. Помните замыкания?
В определенных ситуациях переменная может иметь неверное значение из-за контекста, в котором она находится, например, в обычных циклах for или асинхронных событиях. Это из-за лексической области видимости. Когда создается новая функция, лексическая область видимости сохраняется. Поскольку это не новая функция, лексическая область видимости newVal не сохраняется, и поэтому значение фактически теряет ссылку во время его использования.
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
Что вам нужно сделать, так это использовать сеттер как функцию. При создании новой функции ссылка на переменные сохраняется в лексической области видимости, а currentVal передается самим хуком useState.
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
Это гарантирует, что ваше значение обновляется правильно, потому что функция сеттера вызывается в правильном контексте. Это также можно использовать в других ситуациях, когда полезно воздействовать на текущее значение, React вызывает вашу функцию с первым аргументом в качестве текущего значения.
Встроенные Функции JSX
Вот демонстрация на Codepen встроенной функции JSX:
JSX поддерживает встроенные функции, и он может быть действительно полезен для объявления простой логики со встроенными переменными, если он возвращает элемент JSX.
Вот пример:
function App() { return ( <> {(() => { const darkMode = isDarkMode() if (darkMode) { return ( <div className='dark-mode'></div> ) } else { return ( <div className='light-mode'></div> ) // we can declare JSX anywhere! } })()} // don't forget to call the function! </> ) }
Здесь мы объявляем код внутри JSX. Мы можем запускать произвольный код, и все, что нам нужно сделать, это вернуть функцию JSX для визуализации.
Мы можем сделать это условным или просто выполнить некоторую логику. Обратите внимание на круглые скобки, окружающие встроенную функцию. Также, особенно здесь, где мы вызываем эту функцию, мы могли бы даже передать в нее аргумент из окружающего контекста!
})()}
Это может быть полезно в ситуациях, когда вы хотите воздействовать на структуру данных коллекции более сложным образом, чем это позволяет стандарт .map внутри элемента JSX.
function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
Мы можем запустить некоторый код, чтобы перебрать набор чисел, а затем отобразить их в строке. Если вы используете генератор статических сайтов, такой как Gatsby, этот шаг также будет предварительно выполнен.
Расширение типов
Эта функция, чрезвычайно полезная для создания компонентов, удобных для автозаполнения, позволяет создавать компоненты, расширяющие существующие HTMLElements или другие компоненты. В основном полезно для правильной типизации элементов интерфейса в Typescript, но фактическое применение для JavaScript такое же.
Вот простой пример. Допустим, мы хотим переопределить одно или два свойства элемента button, но при этом даем разработчикам возможность добавлять к кнопке другие свойства. Например, установить type=’button’ или type=’submit’. Очевидно, что мы не хотим воссоздавать весь элемент кнопки, мы просто хотим расширить его существующие свойства и, возможно, добавить еще одно свойство.
import React, { ButtonHTMLAttributes } from 'react'
Сначала мы импортируем класс ButtonHTMLAttributes, тип, который включает в себя свойства HTMLButtonElement. Затем мы объявляем наш интерфейс, добавляя свойство status.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
И, наконец, мы делаем еще пару вещей: используем деструктуризацию ES6, чтобы извлечь нужные нам свойства (status, и children), и объявить любые другие свойства, например rest, также мы возвращаем элемент кнопки со структурированием ES6 для добавления дополнительных свойств к этому элементу.
function Button(props: ButtonProps) { const { status, children, ...rest } = props // rest has any other props return ( <button className={`${status}`} {...rest} // we pass the rest of the props back into the element > {children} </button> ) }
Итак, теперь разработчик может добавить свойства type или любое другое свойство, которое обычно имеет кнопка. Мы предоставили дополнительное свойство className для установки стиля кнопки. Вот весь пример:
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, ...rest } = props return ( <button className={`${status}`} {...rest} > {children} </button> ) }
Это отличный способ создания многократно используемых внутренних компонентов, соответствующих вашим рекомендациям по стилю, без перестройки целых HTML-элементов! Вы можете просто переопределить все свойства.
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, className, ...rest } = props return ( <button className={`${status || ''} ${className || ''}`} {...rest} > {children} </button> ) }
Здесь мы берем свойство className, переданное нашему элементу Button, и вставляем его обратно, с проверкой безопасности в случае существования свойства undefined.
Заключение
React — чрезвычайно мощная библиотека, и есть веская причина, по которой она быстро завоевала популярность. Это отличный набор инструментов для создания эффективных и простых в обслуживании веб-приложений. Он чрезвычайно гибкий и в то же время очень строгий, что может быть невероятно полезным, если вы знаете, как его использовать. Я привел всего лишь несколько API, которые заслуживают внимания и часто игнорируются. Попробуйте их в следующем проекте!
Автор: Gaurav Khanna
Источник: www.smashingmagazine.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен