API útiles de React para crear componentes flexibles con TypeScript

Publicado: 2022-03-10
Resumen rápido ↬ React with JSX es una herramienta fantástica para crear componentes fáciles de usar. Los componentes Typescript hacen que sea un placer absoluto para los desarrolladores integrar sus componentes en sus aplicaciones y explorar sus API. Conozca tres API de React menos conocidas que pueden llevar sus componentes al siguiente nivel y ayudarlo a crear componentes de React aún mejores en este artículo.

¿Alguna vez ha usado React.createElement directamente? ¿Qué pasa con React.cloneElement ? React es más que simplemente transformar tu JSX en HTML. Mucho más, y para ayudarlo a mejorar su conocimiento de las API menos conocidas (pero muy útiles) con las que se envía la biblioteca React. Vamos a repasar algunos de ellos y algunos de sus casos de uso que pueden mejorar drásticamente la integración y la utilidad de sus componentes.

En este artículo, repasaremos algunas API de React útiles que no son tan conocidas pero extremadamente útiles para los desarrolladores web. Los lectores deben tener experiencia con la sintaxis de React y JSX, el conocimiento de Typescript es útil pero no necesario. Los lectores obtendrán todo lo que necesitan saber para mejorar en gran medida los componentes de React cuando los utilicen en las aplicaciones de React.

React.cloneElement

Es posible que la mayoría de los desarrolladores nunca hayan oído hablar de cloneElement o que lo hayan usado alguna vez. Se introdujo hace relativamente poco tiempo para reemplazar la función cloneWithProps ahora en desuso. cloneElement clona un elemento, también le permite fusionar nuevos accesorios con el elemento existente, modificándolos o anulándolos como mejor le parezca. Esto abre opciones extremadamente poderosas para crear API de clase mundial para componentes funcionales. Echa un vistazo a la firma.

 function cloneElement( element, props?, ...children)

Aquí está la versión mecanografiada condensada:

 function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement

Puede tomar un elemento, modificarlo, incluso anular sus elementos secundarios y luego devolverlo como un elemento nuevo. Echa un vistazo al siguiente ejemplo. Digamos que queremos crear un componente TabBar de enlaces. Eso podría parecerse a esto.

 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 es una lista de enlaces, pero necesitamos una forma de definir dos datos, el título del enlace y la URL. Así que querremos que se pase una estructura de datos con esta información. Entonces nuestro desarrollador haría nuestro componente así.

 function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }

Esto es genial, pero ¿qué pasa si el usuario quiere representar elementos de button en lugar de a ? Bueno, podríamos agregar otra propiedad que le diga al componente qué tipo de elemento representar.

Pero puede ver cómo esto se volverá difícil de manejar rápidamente, necesitaríamos admitir más y más propiedades para manejar varios casos de uso y casos extremos para una máxima flexibilidad.

Aquí hay una mejor manera, usando React.cloneElement .

Comenzaremos cambiando nuestra interfaz para hacer referencia al tipo ReactNode . Este es un tipo genérico que abarca todo lo que React puede representar, generalmente JSX Elements, pero también puede ser cadenas e incluso null . Esto es útil para indicarle que desea aceptar componentes de React o JSX como argumentos en línea.

 export interface ITabbarProps { links: ReactNode[] }

Ahora le estamos pidiendo al usuario que nos dé algunos React Elements, y los renderizaremos como queremos.

 function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }

Esto es perfectamente válido y representaría nuestros elementos. Pero nos estamos olvidando de un par de cosas. Para uno, ¡ key ! Queremos agregar claves para que React pueda representar nuestras listas de manera eficiente. También queremos modificar nuestros elementos para hacer las transformaciones necesarias para que se ajusten a nuestro estilo, como className , etc.

¡Podemos hacer esto con React.cloneElement y otra función React.isValidElement para verificar que el argumento se ajuste a lo que esperamos!

¡Más después del salto! Continúe leyendo a continuación ↓

React.isValidElement

Esta función devuelve true si un elemento es un elemento React válido y React puede representarlo. Aquí hay un ejemplo de modificación de los elementos del ejemplo anterior.

 function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }

¡Aquí estamos agregando un accesorio clave a cada elemento que estamos pasando y haciendo que cada enlace esté en negrita al mismo tiempo! Ahora podemos aceptar React Elements arbitrarios como accesorios de esta manera:

 function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }

Podemos anular cualquiera de los accesorios establecidos en un elemento y aceptar fácilmente varios tipos de elementos, lo que hace que nuestro componente sea mucho más flexible y fácil de usar.

La ventaja aquí es que si quisiéramos establecer un controlador onClick personalizado para nuestro botón, podríamos hacerlo. Aceptar elementos de React como argumentos es una forma poderosa de dar flexibilidad al diseño de su componente.

useState Setter Función

¡Usa ganchos! El useState es extremadamente útil y una API fantástica para construir rápidamente el estado en sus componentes de la siguiente manera:

 const [myValue, setMyValue] = useState()

Debido al tiempo de ejecución de JavaScript, puede tener algunos contratiempos. ¿Recuerdas los cierres?

En determinadas situaciones, es posible que una variable no tenga el valor correcto debido al contexto en el que se encuentra, como en bucles for habituales o eventos asincrónicos. Esto se debe al alcance léxico. Cuando se crea una nueva función, se conserva el ámbito léxico. Debido a que no hay una nueva función, el alcance léxico de newVal no se conserva, por lo que el valor se elimina de la referencia en el momento en que se usa.

 setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)

Lo que deberá hacer es utilizar el colocador como una función. Al crear una nueva función, la referencia de las variables se conserva en el ámbito léxico y el currentVal se pasa por el mismo React useState Hook.

 setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)

Esto asegurará que su valor se actualice correctamente porque la función de establecimiento se llama en el contexto correcto. Lo que React hace aquí es llamar a su función en el contexto correcto para que ocurra una actualización de estado de React. Esto también se puede usar en otras situaciones en las que es útil actuar sobre el valor actual. React llama a su función con el primer argumento como el valor actual.

Nota : Para leer más sobre el tema de la sincronización y los cierres, recomiendo leer " useState Lazy Initialization And Function Updates" de Kent C. Dodds.

Funciones JSX en línea

Aquí hay una demostración de Codepen de una función en línea JSX:

Vea el Pen [Hello World in React](https://codepen.io/smashingmag/pen/QWgQQKR) de Gaurav Khanna.

Vea el Pen Hello World en React por Gaurav Khanna.

No es exactamente una API React por decir.

JSX admite funciones en línea y puede ser realmente útil para declarar lógica simple con variables en línea, siempre que devuelva un elemento JSX.

Aquí hay un ejemplo:

 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! </> ) }

Aquí estamos declarando código dentro de JSX, podemos ejecutar código arbitrario y todo lo que tenemos que hacer es devolver una función JSX para que se represente.

Podemos hacerlo condicional, o simplemente realizar alguna lógica. Tome nota de los paréntesis que rodean la función en línea. También particularmente aquí donde estamos llamando a esta función, ¡incluso podríamos pasar un argumento a esta función desde el contexto circundante si quisiéramos!

 })()}

Esto puede ser útil en situaciones en las que desea actuar en una estructura de datos de colección de una manera más compleja que lo que permite un .map estándar dentro de un elemento JSX.

 function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }

Aquí podemos ejecutar un código para recorrer un conjunto de números y luego mostrarlos en línea. Si usa un generador de sitios estáticos como Gatsby, este paso también se calculará previamente.

component extends type

Inmensamente útil para crear componentes fáciles de autocompletar, esta función le permite crear componentes que amplían HTMLElements existentes u otros componentes. Principalmente útil para escribir correctamente una interfaz de elementos en Typescript, pero la aplicación real es la misma para JavaScript.

Aquí hay un ejemplo simple, digamos que queremos anular una o dos propiedades de un elemento de button , pero aún así dar a los desarrolladores la opción de agregar otras propiedades al botón. Como configurar type='button' o type='submit' . Obviamente, no queremos recrear todo el elemento del botón, solo queremos extender sus propiedades existentes y tal vez agregar una propiedad más.

 import React, { ButtonHTMLAttributes } from 'react'

Primero importamos React y la clase ButtonHTMLAttributes , un tipo que abarca los accesorios de un HTMLButtonElement . Puede leer el código fuente de este tipo de interfaz aquí:

Y puede ver que el equipo de React ha vuelto a implementar todas las API de la web en TypeScript para que se pueda verificar el tipo.

A continuación, declaramos nuestra interfaz así, agregando nuestra propiedad status .

 interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }

Y finalmente, hacemos un par de cosas. Usamos la desestructuración de ES6 para extraer los accesorios que nos interesan ( status y elementos children ) y declaramos cualquier otra propiedad como rest . Y en nuestra salida JSX, devolvemos un elemento de botón, con estructuración ES6 para agregar cualquier propiedad adicional 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> ) }

Entonces, ahora un desarrollador puede agregar un accesorio de type o cualquier otro accesorio que normalmente tendría un botón. Le hemos dado una propiedad adicional que hemos utilizado en className para establecer el estilo del botón.

Aquí está el ejemplo completo:

 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> ) }

¡Esto lo convierte en una excelente manera de crear componentes internos reutilizables que se ajusten a sus pautas de estilo sin reconstruir elementos HTML completos! Simplemente puede anular accesorios completos, como configurar className en función del estado o permitir que también se pasen nombres de clase adicionales.

 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> ) }

Aquí tomamos el className de la propiedad que se pasó a nuestro elemento Button y lo volvemos a insertar, con una verificación de seguridad en el caso de que la propiedad no esté undefined .

Conclusión

React es una biblioteca extremadamente poderosa, y hay una buena razón por la que ha ganado popularidad rápidamente. Le brinda un excelente conjunto de herramientas para crear aplicaciones web de alto rendimiento y fáciles de mantener. Es extremadamente flexible y muy estricto al mismo tiempo, lo que puede ser increíblemente útil si sabes cómo usarlo. Estas son solo algunas API que son dignas de mención y se pasan por alto en gran medida. ¡Pruébalos en tu próximo proyecto!

Para leer más sobre las últimas API de React, ganchos, recomendaría leer useHooks(). La hoja de trucos de Typescript también tiene información excelente para React y Typescript Hooks.