APIs React úteis para construir componentes flexíveis com TypeScript
Publicados: 2022-03-10 Você já usou React.createElement
diretamente? E quanto React.cloneElement
? React é mais do que apenas transformar seu JSX em HTML. Muito mais, e para ajudá-lo a aumentar seu conhecimento de APIs menos conhecidas (mas muito úteis) com as quais a biblioteca React é fornecida. Vamos examinar alguns deles e alguns de seus casos de uso que podem melhorar drasticamente a integração e a utilidade de seus componentes.
Neste artigo, veremos algumas APIs React úteis que não são tão conhecidas, mas extremamente úteis para desenvolvedores web. Os leitores devem ter experiência com sintaxe React e JSX, o conhecimento de Typescript é útil, mas não necessário. Os leitores terão tudo o que precisam saber para aprimorar bastante os componentes React ao usá-los em aplicativos React.
React.cloneElement
A maioria dos desenvolvedores pode nunca ter ouvido falar de cloneElement
ou já o usou. Foi introduzido relativamente recentemente para substituir a função cloneWithProps
, agora obsoleta. cloneElement
clona um elemento, ele também permite mesclar novos adereços com o elemento existente, modificando-os ou substituindo-os como achar melhor. Isso abre opções extremamente poderosas para construir APIs de classe mundial para componentes funcionais. Dê uma olhada na assinatura.
function cloneElement( element, props?, ...children)
Aqui está a versão condensada do Typescript:
function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement
Você pode pegar um elemento, modificá-lo, até mesmo substituir seus filhos e, em seguida, devolvê-lo como um novo elemento. Dê uma olhada no exemplo a seguir. Digamos que queremos criar um componente TabBar de links. Isso pode parecer algo assim.
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> )} </> ) }
A TabBar é uma lista de links, mas precisamos definir dois dados, o título do link e a URL. Então vamos querer uma estrutura de dados passada com esta informação. Então nosso desenvolvedor faria nosso componente assim.
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
Isso é ótimo, mas e se o usuário quiser renderizar elementos de button
em vez de a
? Bem, poderíamos adicionar outra propriedade que informa ao componente qual tipo de elemento renderizar.
Mas você pode ver como isso se tornará rapidamente complicado, precisaríamos oferecer suporte a mais e mais propriedades para lidar com vários casos de uso e casos de borda para obter o máximo de flexibilidade.
Aqui está uma maneira melhor, usando React.cloneElement
.
Começaremos alterando nossa interface para referenciar o tipo ReactNode
. Este é um tipo genérico que engloba qualquer coisa que o React possa renderizar, normalmente JSX Elements, mas também pode ser strings e até null
. Isso é útil para designar você para aceitar componentes React ou JSX como argumentos embutidos.
export interface ITabbarProps { links: ReactNode[] }
Agora estamos pedindo ao usuário que nos dê alguns React Elements, e vamos renderizá-los como queremos.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
Isso é perfeitamente válido e renderia nossos elementos. Mas estamos esquecendo algumas coisas. Por um lado, key
! Queremos adicionar chaves para que o React possa renderizar nossas listas com eficiência. Também queremos alterar nossos elementos para fazer as transformações necessárias para que se encaixem em nosso estilo, como className
e assim por diante.
Podemos fazer isso com React.cloneElement
e outra função React.isValidElement
para verificar se o argumento está de acordo com o que esperamos!
React.isValidElement
Esta função retorna true
se um elemento for um React Element válido e o React puder renderizá-lo. Aqui está um exemplo de modificação dos elementos do exemplo anterior.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
Aqui estamos adicionando um suporte de chave a cada elemento que estamos passando e tornando todos os links em negrito ao mesmo tempo! Agora podemos aceitar React Elements arbitrários como props assim:
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
Podemos substituir qualquer um dos adereços definidos em um elemento e aceitar facilmente vários tipos de elementos, tornando nosso componente muito mais flexível e fácil de usar.
“
A vantagem aqui é que se quiséssemos definir um manipulador onClick
personalizado para nosso botão, poderíamos fazê-lo. Aceitar os próprios elementos do React como argumentos é uma maneira poderosa de dar flexibilidade ao design do seu componente.
Função useState
Setter
Use ganchos! O gancho useState
é extremamente útil e uma API fantástica para construir rapidamente o estado em seus componentes da seguinte forma:
const [myValue, setMyValue] = useState()
Devido ao tempo de execução do JavaScript, ele pode ter alguns problemas. Lembra dos fechamentos?
Em determinadas situações, uma variável pode não ser o valor correto devido ao contexto em que está, como em loops for comumente ou eventos assíncronos. Isso é por causa do escopo lexical. Quando uma nova função é criada, o escopo léxico é preservado. Como não há uma nova função, o escopo léxico de newVal
não é preservado e, portanto, o valor é realmente desreferenciado no momento em que é usado.
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
O que você precisa fazer é utilizar o setter como uma função. Ao criar uma nova função, a referência de variáveis é preservada no escopo léxico e o currentVal é passado pelo próprio Hook useState do React.
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
Isso garantirá que seu valor seja atualizado corretamente porque a função setter é chamada no contexto correto. O que o React faz aqui é chamar sua função no contexto correto para que uma atualização de estado do React ocorra. Isso também pode ser usado em outras situações em que é útil agir no valor atual, o React chama sua função com o primeiro argumento como o valor atual.
Nota : Para leitura adicional sobre o tópico de async e encerramentos, recomendo a leitura de “ useState
Lazy Initialization And Function Updates” de Kent C. Dodds.
Funções JSX Inline
Aqui está uma demonstração do Codepen de uma função inline JSX:
Não é exatamente uma API React, por exemplo.
O JSX suporta funções inline e pode ser muito útil para declarar lógica simples com variáveis inline, desde que retorne um elemento JSX.
“
Aqui está um exemplo:
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! </> ) }
Aqui estamos declarando código dentro do JSX, podemos executar código arbitrário e tudo o que temos a fazer é retornar uma função JSX para ser renderizada.
Podemos torná-lo condicional, ou simplesmente executar alguma lógica. Observe os parênteses em torno da função inline. Também particularmente aqui onde estamos chamando esta função, poderíamos até passar um argumento para esta função do contexto circundante, se quiséssemos!
})()}
Isso pode ser útil em situações em que você deseja atuar em uma estrutura de dados de coleção de uma maneira mais complexa do que um .map
padrão permite dentro de um elemento JSX.
function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
Aqui podemos executar algum código para percorrer um conjunto de números e exibi-los em linha. Se você usar um gerador de site estático como o Gatsby, essa etapa também será pré-computada.
component extends type
Imensamente útil para criar componentes amigáveis ao autocomplete, esse recurso permite que você crie componentes que estendem HTMLElements
existentes ou outros componentes. Principalmente útil para digitar corretamente uma interface de elementos em Typescript, mas o aplicativo real é o mesmo para JavaScript.
Aqui está um exemplo simples, digamos que queremos substituir uma ou duas propriedades de um elemento de button
, mas ainda dar aos desenvolvedores a opção de adicionar outras propriedades ao botão. Como definir type='button'
ou type='submit'
. Obviamente, não queremos recriar todo o elemento do botão, queremos apenas estender suas propriedades existentes e talvez adicionar mais uma prop.
import React, { ButtonHTMLAttributes } from 'react'
Primeiro importamos React e a classe ButtonHTMLAttributes
, um tipo que engloba as props de um HTMLButtonElement
. Você pode ler o código-fonte para este tipo de interface aqui:
E você pode ver que a equipe do React reimplementou todas as APIs da web no TypeScript para que possam ser verificadas.
Em seguida, declaramos nossa interface assim, adicionando nossa propriedade status
.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
E, finalmente, fazemos algumas coisas. Usamos a desestruturação ES6 para extrair os adereços que nos interessam ( status
e children
) e declaramos quaisquer outras propriedades como rest
. E em nossa saída JSX, retornamos um elemento de botão, com estruturação ES6 para adicionar quaisquer propriedades adicionais a este elemento.
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> ) }
Então agora um desenvolvedor pode adicionar um type
prop ou qualquer outro prop que um botão normalmente teria. Demos uma prop adicional que usamos no className
para definir o estilo do botão.
Aqui está o exemplo inteiro:
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> ) }
Isso é uma ótima maneira de criar componentes internos reutilizáveis que estão em conformidade com suas diretrizes de estilo sem reconstruir elementos HTML inteiros! Você pode simplesmente substituir props inteiras, como definir o className
com base no status ou permitir que nomes de classes adicionais sejam passados também.
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> ) }
Aqui pegamos o prop className
passado para nosso elemento Button e o inserimos de volta, com uma verificação de segurança no caso do prop ser undefined
.
Conclusão
React é uma biblioteca extremamente poderosa, e há uma boa razão pela qual ela ganhou popularidade rapidamente. Ele oferece um ótimo conjunto de ferramentas para criar aplicativos da Web de fácil manutenção e de alto desempenho. É extremamente flexível e, ao mesmo tempo, muito rigoroso, o que pode ser incrivelmente útil se você souber como usá-lo. Essas são apenas algumas APIs que são dignas de nota e são amplamente ignoradas. Experimente-os em seu próximo projeto!
Para ler mais sobre as últimas APIs do React, ganchos, eu recomendaria a leitura de useHooks(). O Typescript Cheatsheet também tem ótimas informações para React e Typescript Hooks.