Полезные API React для создания гибких компонентов с помощью TypeScript
Вы когда-нибудь использовали React.createElement напрямую? А как на счет React.cloneElement? React — это больше, чем просто преобразование JSX в HTML. Гораздо больше, и чтобы помочь вам повысить уровень ваших знаний о менее известных (но очень полезных) API, с которыми поставляется библиотека React, мы собираемся рассмотреть некоторые из них и некоторые варианты их использования, которые могут значительно улучшить интеграцию и полезность ваших компонентов.
В этой статье мы рассмотрим несколько полезных API React, которые не так широко известны, но чрезвычайно полезны для веб-разработчиков. Читатели должны иметь опыт работы с синтаксисом React и JSX, знание Typescript желательно, но не обязательно. Читатели познакомятся с тем, что им нужно знать, чтобы значительно улучшить компоненты React при их использовании в своих приложениях.
React.cloneElement
Содержание статьи:
Большинство разработчиков, возможно, никогда не слышали о cloneElement и никогда не использовали его. Он был введен относительно недавно для замены устаревшей функции cloneWithProps. СloneElement клонирует элемент, а также позволяет объединять новые свойства с существующим элементом, изменяя или переопределяя их по своему усмотрению. Это открывает чрезвычайно мощные возможности для создания API для функциональных компонентов. Взгляните на его сигнатуру:
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас! JavaScript function cloneElement( element, props?, …children)
1 | function cloneElement( element, props?, …children) |
Вот сокращенная версия Typescript:
JavaScript function cloneElement( element: ReactElement, props?: HTMLAttributes, …children: ReactNode[]): ReactElement
1234 | function cloneElement( element: ReactElement, props?: HTMLAttributes, …children: ReactNode[]): ReactElement |
Вы можете взять элемент, изменить его, даже переопределить его дочерние элементы, а затем вернуть его как новый элемент. Взгляните на следующий пример. Допустим, мы хотим создать компонент ссылок TabBar. Это может выглядеть примерно так.
JavaScript 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> )} </> ) }
12345678910111213 | 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-адреса. Итак, нам нужна структура данных, передаваемая с этой информацией. Так что наш разработчик сделал бы этот компонент таким.
JavaScript function App() { return ( <Tabbar links={[ {title: ‘First’, url: ‘/first’}, {title: ‘Second’, url: ‘/second’}] } /> ) }
12345678 | function App() { return ( <Tabbar links={[ {title: ‘First’, url: ‘/first’}, {title: ‘Second’, url: ‘/second’}] } /> )} |
Это здорово, но что, если пользователь хочет отображать элементы button вместо элементов a? Что ж, мы могли бы добавить еще одно свойство, которое сообщает компоненту, какой тип нужно отображать.
Но вы можете видеть, как быстро код станет громоздким, и нам потребуется поддерживать все больше и больше свойств для обработки различных вариантов использования и граничных случаев для максимальной гибкости. Вот лучший способ — использовать React.cloneElement.
Мы начнем с изменения нашего интерфейса, чтобы он ссылался на тип ReactNode. Это общий тип, который охватывает все, что может визуализировать React, обычно элементы JSX, но также могут быть string и даже null. Это полезно для обозначения того, что вы хотите принимать в качестве встроенных аргументов: компоненты React или JSX.
JavaScript export interface ITabbarProps { links: ReactNode[] }
123 | export interface ITabbarProps { links: ReactNode[]} |
Теперь мы просим пользователя предоставить нам несколько элементов React, и мы отрендерим их так, как захотим.
JavaScript function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
123456789 | 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 может его отобразить. Вот пример изменения элементов из предыдущего примера.
JavaScript function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: ‘bold’}) )} </> ) }
123456789 | function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: ‘bold’}) )} </> )} |
Здесь мы добавляем свойство key к каждому элементу, который мы передаем, и одновременно выделяем каждую ссылку жирным шрифтом! Теперь мы можем принимать произвольные элементы React в качестве свойств:
JavaScript function App() { return ( <Tabbar links={[ <a href=’/first’>First</a>, <button type=’button’>Second</button> ]} /> ) }
12345678 | function App() { return ( <Tabbar links={[ <a href=’/first’>First</a>, <button type=’button’>Second</button> ]} /> )} |
Мы можем переопределить любые свойства, установленные для элемента, и легко принять различные типы элементов, что делает наш компонент более гибким и простым в использовании.
Преимущество здесь в том, что если бы мы хотели установить собственный обработчик onClick для нашей кнопки, мы могли бы это сделать. Принятие элементов React в качестве аргументов — мощный способ придать гибкость дизайну вашего компонента.
Функция сеттера useState
Используйте хуки! Хук useState является чрезвычайно полезным и фантастическим API для быстрого создания состояния ваших компонентов следующим образом:
JavaScript const [myValue, setMyValue] = useState()
1 | const [myValue, setMyValue] = useState() |
Во время выполнения JavaScript возможны некоторые сбои. Помните замыкания?
В определенных ситуациях переменная может иметь неверное значение из-за контекста, в котором она находится, например, в обычных циклах for или асинхронных событиях. Это из-за лексической области видимости. Когда создается новая функция, лексическая область видимости сохраняется. Поскольку это не новая функция, лексическая область видимости newVal не сохраняется, и поэтому значение фактически теряет ссылку во время его использования.
JavaScript setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
123 | setTimeout(() => { setMyValue(newVal) // this will not work}, 1000) |
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Что вам нужно сделать, так это использовать сеттер как функцию. При создании новой функции ссылка на переменные сохраняется в лексической области видимости, а currentVal передается самим хуком useState.
JavaScript setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
12345 | setTimeout(() => { setMyValue((currentVal) => { return newVal })}, 1000) |
Это гарантирует, что ваше значение обновляется правильно, потому что функция сеттера вызывается в правильном контексте. Это также можно использовать в других ситуациях, когда полезно воздействовать на текущее значение, React вызывает вашу функцию с первым аргументом в качестве текущего значения.
Встроенные Функции JSX
Вот демонстрация на Codepen встроенной функции JSX:
JSX поддерживает встроенные функции, и он может быть действительно полезен для объявления простой логики со встроенными переменными, если он возвращает элемент JSX.
Вот пример:
JavaScript 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! </> ) }
12345678910111213141516171819 | 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 для визуализации.
Мы можем сделать это условным или просто выполнить некоторую логику. Обратите внимание на круглые скобки, окружающие встроенную функцию. Также, особенно здесь, где мы вызываем эту функцию, мы могли бы даже передать в нее аргумент из окружающего контекста!
JavaScript })()}
1 | })()} |
Это может быть полезно в ситуациях, когда вы хотите воздействовать на структуру данных коллекции более сложным образом, чем это позволяет стандарт .map внутри элемента JSX.
JavaScript function App() { return ( <> {(() => { let str = ” for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
12345678910111213 | 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’. Очевидно, что мы не хотим воссоздавать весь элемент кнопки, мы просто хотим расширить его существующие свойства и, возможно, добавить еще одно свойство.
JavaScript import React, { ButtonHTMLAttributes } from ‘react’
1 | import React, { ButtonHTMLAttributes } from ‘react’ |
Сначала мы импортируем класс ButtonHTMLAttributes, тип, который включает в себя свойства HTMLButtonElement. Затем мы объявляем наш интерфейс, добавляя свойство status.
JavaScript interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: ‘primary’ | ‘info’ | ‘danger’ }
123 | interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: ‘primary’ | ‘info’ | ‘danger’} |
И, наконец, мы делаем еще пару вещей: используем деструктуризацию ES6, чтобы извлечь нужные нам свойства (status, и children), и объявить любые другие свойства, например rest, также мы возвращаем элемент кнопки со структурированием ES6 для добавления дополнительных свойств к этому элементу.
JavaScript 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> ) }
1234567891011 | 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 для установки стиля кнопки. Вот весь пример:
JavaScript 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> ) }
1234567891011121314151617 | 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-элементов! Вы можете просто переопределить все свойства.
JavaScript 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> ) }
1234567891011121314151617 | 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
Источник: webformyself.com