¿Estás desarrollando con React o simplemente estás interesado en esta tecnología? Entonces bienvenido a mi nuevo proyecto: Total React .
Introducción
He trabajado con React durante 5 años, sin embargo, cuando se trata de la estructura de la aplicación o su apariencia (diseño), es difícil nombrar enfoques universales.
Al mismo tiempo, existen ciertas técnicas de codificación que le permiten garantizar el soporte y la escalabilidad a largo plazo de sus proyectos.
Este artículo es una especie de conjunto de reglas para desarrollar aplicaciones React que han demostrado ser efectivas para mí y para los equipos con los que he trabajado.
Estas reglas cubren los componentes, la estructura de la aplicación, las pruebas, el estilo, la gestión del estado y la recuperación de datos. Los ejemplos proporcionados se han simplificado intencionadamente para centrarse en principios generales en lugar de una implementación específica.
Los enfoques propuestos no son la verdad última. Esta es sólo mi opinión. Hay muchas formas diferentes de realizar la misma tarea.
Componentes
Componentes funcionales
Dé preferencia a los componentes funcionales, tienen una sintaxis más simple. Carecen de métodos de ciclo de vida, constructores y código repetitivo. Te permiten implementar la misma lógica que los componentes de la clase, pero con menos esfuerzo y de forma más descriptiva (el código del componente es más fácil de leer).
Utilice componentes funcionales hasta que necesite fusibles. El modelo mental a tener en cuenta será mucho más sencillo.
// ""
class Counter extends React.Component {
state = {
counter: 0,
}
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({ counter: this.state.counter + 1 })
}
render() {
return (
<div>
<p> : {this.state.counter}</p>
<button onClick={this.handleClick}></button>
</div>
)
}
}
//
function Counter() {
const [counter, setCounter] = useState(0)
handleClick = () => setCounter(counter + 1)
return (
<div>
<p> : {counter}</p>
<button onClick={handleClick}></button>
</div>
)
}
Componentes consistentes (secuenciales)
Siga el mismo estilo al crear componentes. Coloque las funciones auxiliares en el mismo lugar, use la misma exportación (por defecto o por nombre) y use la misma convención de nomenclatura para los componentes.
Cada enfoque tiene sus propias ventajas y desventajas.
No importa cómo exporte los componentes, en la parte inferior o en la definición, solo siga una regla.
Nombres de componentes
Siempre nombre los componentes. Esto ayuda a analizar el seguimiento de la pila de errores cuando se utilizan las herramientas de desarrollo de React.
También le ayuda a determinar qué componente está desarrollando actualmente.
//
export default () => <form>...</form>
//
export default function Form() {
return <form>...</form>
}
Funciones secundarias
Las funciones que no requieren los datos almacenados en el componente deben estar fuera (fuera) del componente. El lugar ideal para esto es antes de la definición del componente, de modo que el código se pueda examinar de arriba a abajo.
Esto reduce el "ruido" del componente, solo lo esencial queda en él.
//
function Component({ date }) {
function parseDate(rawDate) {
...
}
return <div> {parseDate(date)}</div>
}
//
function parseDate(date) {
...
}
function Component({ date }) {
return <div> {parseDate(date)}</div>
}
Debe haber un número mínimo de funciones auxiliares dentro del componente. Colóquelos afuera, pasando valores del estado como argumentos.
Siguiendo las reglas para crear funciones "limpias", es más fácil rastrear errores y extender el componente.
// ""
export default function Component() {
const [value, setValue] = useState('')
function isValid() {
// ...
}
return (
<>
<input
value={value}
onChange={e => setValue(e.target.value)}
onBlur={validateInput}
/>
<button
onClick={() => {
if (isValid) {
// ...
}
}}
>
</button>
</>
)
}
//
function isValid(value) {
// ...
}
export default function Component() {
const [value, setValue] = useState('')
return (
<>
<input
value={value}
onChange={e => setValue(e.target.value)}
onBlur={validateInput}
/>
<button
onClick={() => {
if (isValid(value)) {
// ...
}
}}
>
</button>
</>
)
}
Marcado estático (duro)
No cree marcas estáticas para navegación, filtros o listas. En su lugar, crea un objeto con configuraciones y recorrelo.
Esto significa que solo necesita cambiar el marcado y los elementos en un lugar, si es necesario.
//
function Filters({ onFilterClick }) {
return (
<>
<p> </p>
<ul>
<li>
<div onClick={() => onFilterClick('fiction')}> </div>
</li>
<li>
<div onClick={() => onFilterClick('classics')}>
</div>
</li>
<li>
<div onClick={() => onFilterClick('fantasy')}></div>
</li>
<li>
<div onClick={() => onFilterClick('romance')}></div>
</li>
</ul>
</>
)
}
//
const GENRES = [
{
identifier: 'fiction',
name: ' ',
},
{
identifier: 'classics',
name: '',
},
{
identifier: 'fantasy',
name: '',
},
{
identifier: 'romance',
name: '',
},
]
function Filters({ onFilterClick }) {
return (
<>
<p> </p>
<ul>
{GENRES.map(genre => (
<li>
<div onClick={() => onFilterClick(genre.identifier)}>
{genre.name}
</div>
</li>
))}
</ul>
</>
)
}
Dimensiones de los componentes
Un componente es solo una función que toma accesorios y devuelve marcado. Siguen los mismos principios de diseño.
Si una función realiza demasiadas tareas, mueva parte de la lógica a otra función. Lo mismo ocurre con los componentes: si un componente contiene una funcionalidad demasiado compleja, divídalo en varios componentes.
Si parte del marcado es complejo, incluye bucles o condiciones, extráigalo en un componente separado.
Confíe en los accesorios y las devoluciones de llamada para la interacción y la recuperación de datos. El número de líneas de código no siempre es un criterio objetivo para su calidad. Recuerde siempre ser receptivo y abstraído.
Comentarios en JSX
Si necesita una explicación de lo que está sucediendo, cree un bloque de comentarios y agregue allí la información necesaria. El marcado es parte de la lógica, así que si sientes que necesitas comentar una parte, hazlo.
function Component(props) {
return (
<>
{/* , */}
{user.subscribed ? null : <SubscriptionPlans />}
</>
)
}
Rompedores de circuito
Un error en el componente no debería romper la interfaz de usuario. Hay casos raros en los que queremos que un error crítico provoque un bloqueo o una redirección de la aplicación. En la mayoría de los casos, basta con eliminar un determinado elemento de la pantalla.
En una función que solicita datos, podemos tener cualquier número de bloques try / catch. Utilice fusibles no solo en el nivel superior de su aplicación, sino que envuelva todos los componentes que podrían generar una excepción para evitar una cascada de errores.
function Component() {
return (
<Layout>
<ErrorBoundary>
<CardWidget />
</ErrorBoundary>
<ErrorBoundary>
<FiltersWidget />
</ErrorBoundary>
<div>
<ErrorBoundary>
<ProductList />
</ErrorBoundary>
</div>
</Layout>
)
}
Puntales de desestructuración
La mayoría de los componentes son funciones que toman accesorios y devuelven marcado. En una función normal, usamos argumentos que se le pasan directamente, por lo que en el caso de los componentes tiene sentido seguir un enfoque similar. No es necesario repetir "accesorios" en todas partes.
La razón para no utilizar la desestructuración podría ser la diferencia entre los estados externos e internos. Sin embargo, en una función normal, no hay diferencia entre argumentos y variables. No es necesario complicar las cosas.
// "props"
function Input(props) {
return <input value={props.value} onChange={props.onChange} />
}
//
function Component({ value, onChange }) {
const [state, setState] = useState('')
return <div>...</div>
}
Número de accesorios
La respuesta a la pregunta sobre el número de accesorios es muy subjetiva. El número de accesorios pasados a un componente se correlaciona con el número de variables utilizadas por el componente. Cuantos más accesorios se pasen al componente, mayor será su responsabilidad (es decir, el número de tareas resueltas por el componente).
Una gran cantidad de accesorios puede indicar que el componente está haciendo demasiado.
Si se pasan más de 5 accesorios a un componente, pienso en la necesidad de dividirlo. En algunos casos, el componente solo necesita muchos datos. Por ejemplo, un campo de entrada de texto puede necesitar muchos accesorios. Por otro lado, esta es una señal segura de que parte de la lógica debe extraerse en un componente separado.
Tenga en cuenta: cuantos más accesorios recibe un componente, más a menudo se vuelve a dibujar.
Pasar un objeto en lugar de primitivas
Una forma de reducir el número de accesorios pasados es pasar un objeto en lugar de primitivas. En lugar de, por ejemplo, transmitir el nombre del usuario, su dirección de correo electrónico, etc. uno a la vez, puede agruparlos. También facilitará la adición de nuevos datos.
//
<UserProfile
bio={user.bio}
name={user.name}
email={user.email}
subscription={user.subscription}
/>
// ,
<UserProfile user={user} />
Representación condicional
En algunos casos, el uso de cálculos cortos (el operador lógico AND &&) para la representación condicional puede resultar en que se muestre 0 en la interfaz de usuario. Para evitar esto, utilice el operador ternario. El único inconveniente de este enfoque es un poco más de código.
El operador && reduce la cantidad de código, lo cual es genial. Ternarnik es más "detallado", pero siempre funciona correctamente. Además, se hace menos costoso agregar una alternativa según sea necesario.
//
function Component() {
const count = 0
return <div>{count && <h1>: {count}</h1>}</div>
}
// ,
function Component() {
const count = 0
return <div>{count ? <h1>: {count}</h1> : null}</div>
}
Operadores ternarios anidados
Los operadores ternarios se vuelven difíciles de leer después del primer nivel de anidamiento. Aunque los ternaries ahorran espacio, es mejor expresar sus intenciones de una manera explícita y obvia.
//
isSubscribed ? (
<ArticleRecommendations />
) : isRegistered ? (
<SubscribeCallToAction />
) : (
<RegisterCallToAction />
)
//
function CallToActionWidget({ subscribed, registered }) {
if (subscribed) {
return <ArticleRecommendations />
}
if (registered) {
return <SubscribeCallToAction />
}
return <RegisterCallToAction />
}
function Component() {
return (
<CallToActionWidget
subscribed={subscribed}
registered={registered}
/>
)
}
Liza
Recorrer los elementos de una lista es una tarea común, generalmente realizada con el método "map ()". Sin embargo, en un componente que contiene mucho marcado, la sangría adicional y la sintaxis "map ()" no mejoran la legibilidad.
Si necesita iterar sobre los elementos, extráigalos en un componente separado, incluso si el marcado es pequeño. El componente principal no necesita detalles, solo necesita "saber" que la lista se está procesando en un lugar determinado.
La iteración se puede dejar en un componente cuyo único propósito es mostrar la lista. Si el marcado de la lista es complejo y largo, es mejor extraerlo en un componente separado.
//
function Component({ topic, page, articles, onNextPage }) {
return (
<div>
<h1>{topic}</h1>
{articles.map(article => (
<div>
<h3>{article.title}</h3>
<p>{article.teaser}</p>
<img src={article.image} />
</div>
))}
<div> {page}</div>
<button onClick={onNextPage}></button>
</div>
)
}
//
function Component({ topic, page, articles, onNextPage }) {
return (
<div>
<h1>{topic}</h1>
<ArticlesList articles={articles} />
<div> {page}</div>
<button onClick={onNextPage}></button>
</div>
)
}
Accesorios predeterminados
Una forma de definir los accesorios predeterminados es agregar un atributo "defaultProps" al componente. Sin embargo, con este enfoque, la función del componente y los valores de sus argumentos estarán en diferentes lugares.
Por lo tanto, es más preferible asignar valores "predeterminados" al desestructurar los accesorios. Esto hace que el código sea más fácil de leer de arriba a abajo y mantiene las definiciones y los valores en un solo lugar.
//
function Component({ title, tags, subscribed }) {
return <div>...</div>
}
Component.defaultProps = {
title: '',
tags: [],
subscribed: false,
}
//
function Component({ title = '', tags = [], subscribed = false }) {
return <div>...</div>
}
Funciones de render anidadas
Si necesita extraer lógica o marcado de un componente, no lo ponga en una función en el mismo componente. Un componente es una función. Esto significa que la parte extraída del código se representará como una función anidada.
Esto significa que la función anidada tendrá acceso al estado y los datos de la función externa. Esto hace que el código sea menos legible: ¿qué hace esta función (de qué es responsable)?
Mueva la función anidada a un componente separado, asígnele un nombre y confíe en accesorios en lugar de cierres.
//
function Component() {
function renderHeader() {
return <header>...</header>
}
return <div>{renderHeader()}</div>
}
//
import Header from '@modules/common/components/Header'
function Component() {
return (
<div>
<Header />
</div>
)
}
Administración del Estado
Cajas de cambios
A veces necesitamos una forma más poderosa de definir y administrar el estado que "useState ()". Intente usar "useReducer ()" antes de usar bibliotecas de terceros. Es una gran herramienta para administrar estados complejos sin requerir dependencias.
Combinado con el contexto y TypeScript, useReducer () puede ser muy poderoso. Desafortunadamente, no se usa con mucha frecuencia. La gente prefiere utilizar bibliotecas especiales.
Si necesita varias piezas de estado, muévalas al reductor:
//
const TYPES = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large'
}
function Component() {
const [isOpen, setIsOpen] = useState(false)
const [type, setType] = useState(TYPES.LARGE)
const [phone, setPhone] = useState('')
const [email, setEmail] = useState('')
const [error, setError] = useSatte(null)
return (
// ...
)
}
//
const TYPES = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large'
}
const initialState = {
isOpen: false,
type: TYPES.LARGE,
phone: '',
email: '',
error: null
}
const reducer = (state, action) => {
switch (action.type) {
...
default:
return state
}
}
function Component() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
// ...
)
}
Hooks versus HOC y accesorios de renderizado
En algunos casos, necesitamos "fortalecer" un componente o proporcionarle acceso a datos externos. Hay tres formas de hacer esto: componentes de orden superior (HOC), renderizado a través de accesorios y ganchos.
La forma más eficaz es utilizar ganchos. Cumplen plenamente con la filosofía de que un componente es una función que utiliza otras funciones. Los Hooks le permiten acceder a múltiples fuentes que contienen funcionalidades externas sin la amenaza de un conflicto entre estas fuentes. La cantidad de ganchos no importa, siempre sabemos de dónde obtuvimos el valor.
Los HOC reciben valores como accesorios. No siempre es obvio de dónde provienen los valores, del componente padre o de su envoltorio. Además, encadenar múltiples accesorios es una conocida fuente de errores.
El uso de accesorios de renderizado conduce a un anidamiento profundo y una legibilidad deficiente. Colocar varios componentes con accesorios de renderizado en el mismo árbol agrava aún más la situación. Además, solo usan valores en el marcado, por lo que la lógica para obtener los valores debe escribirse aquí o recibirse desde afuera.
En el caso de los ganchos, estamos trabajando con valores simples que son fáciles de rastrear y no se mezclan con JSX.
// -
function Component() {
return (
<>
<Header />
<Form>
{({ values, setValue }) => (
<input
value={values.name}
onChange={e => setValue('name', e.target.value)}
/>
<input
value={values.password}
onChange={e => setValue('password', e.target.value)}
/>
)}
</Form>
<Footer />
</>
)
}
//
function Component() {
const [values, setValue] = useForm()
return (
<>
<Header />
<input
value={values.name}
onChange={e => setValue('name', e.target.value)}
/>
<input
value={values.password}
onChange={e => setValue('password', e.target.value)}
/>
<Footer />
</>
)
}
Bibliotecas para obtener datos
Muy a menudo, los datos del estado "provienen" de la API. Necesitamos almacenarlos en la memoria, actualizarlos y recibirlos en varios lugares.
Las bibliotecas modernas como React Query proporcionan una buena cantidad de herramientas para manipular datos externos. Podemos almacenar datos en caché, eliminarlos y solicitar otros nuevos. Estas herramientas también se pueden utilizar para enviar datos, activar una actualización de otro dato, etc.
Trabajar con datos externos es aún más fácil si usa un cliente GraphQL como Apollo . Implementa el concepto de estado del cliente listo para usar.
Bibliotecas de gestión estatal
En la gran mayoría de los casos, no necesitamos ninguna biblioteca para administrar el estado de la aplicación. Solo se requieren en aplicaciones muy grandes con un estado muy complejo. En tales situaciones, utilizo una de dos soluciones: Recoil o Redux .
Modelos mentales componentes
Contenedor y Representante
Por lo general, es habitual dividir los componentes en dos grupos: representantes y contenedores o "inteligentes" y "estúpidos".
La conclusión es que algunos componentes no contienen estado ni funcionalidad. Solo los llama el componente principal con algunos accesorios. El componente contenedor, a su vez, contiene cierta lógica empresarial, envía solicitudes para recibir datos y administra el estado.
Este modelo mental en realidad describe el patrón de diseño MVC para aplicaciones del lado del servidor. Ella trabaja muy bien allí.
Pero en las aplicaciones cliente modernas, este enfoque no se justifica. Poner toda la lógica en múltiples componentes conduce a una hinchazón excesiva. Esto lleva al hecho de que un componente resuelve demasiados problemas. El código de dicho componente es difícil de mantener. A medida que la aplicación crece, mantener el código en un estado adecuado se vuelve casi imposible.
Componentes con estado y sin estado
Divida los componentes en componentes con estado y sin estado. El modelo mental mencionado anteriormente sugiere que una pequeña cantidad de componentes debe impulsar la lógica de toda la aplicación. Este modelo asume la división de la lógica en el máximo número posible de componentes.
Los datos deben estar lo más cerca posible del componente en el que se utilizan. Cuando usamos el cliente GrapQL, recibimos datos en un componente que muestra estos datos. Incluso si no es un componente de nivel superior. No piense en contenedores, piense en la responsabilidad de los componentes. Determinar el componente más apropiado para mantener una parte del estado.
Por ejemplo, un componente <Form /> debe contener datos de formulario. El componente <Input /> debe recibir valores y devolver llamadas. El componente <Botón /> debe notificar al formulario sobre el deseo del usuario de enviar datos para su procesamiento, etc.
¿Quién es el responsable de validar el formulario? ¿Campo de entrada? Esto significará que este componente es responsable de la lógica empresarial de la aplicación. ¿Cómo informará al formulario sobre un error? ¿Cómo se actualizará el estado de error? ¿El formulario "sabrá" acerca de dicha actualización? Si se produce un error, ¿será posible enviar los datos para su procesamiento?
Cuando surgen tales preguntas, se hace evidente que existe una confusión de responsabilidades. En este caso, es mejor que la "entrada" siga siendo un componente sin estado y reciba mensajes de error del formulario.
Estructura de la aplicación
Agrupación por ruta / módulo
La agrupación por contenedores y componentes dificulta el aprendizaje de la aplicación. Determinar a qué parte de una aplicación pertenece un componente en particular supone una familiaridad "cercana" con todo el código base.
No todos los componentes son iguales: algunos se utilizan a nivel mundial, otros están diseñados para satisfacer necesidades específicas. Esta estructura es adecuada para pequeños proyectos. Sin embargo, para proyectos medianos a grandes, dicha estructura es inaceptable.
//
├── containers
| ├── Dashboard.jsx
| ├── Details.jsx
├── components
| ├── Table.jsx
| ├── Form.jsx
| ├── Button.jsx
| ├── Input.jsx
| ├── Sidebar.jsx
| ├── ItemCard.jsx
// /
├── modules
| ├── common
| | ├── components
| | | ├── Button.jsx
| | | ├── Input.jsx
| ├── dashboard
| | ├── components
| | | ├── Table.jsx
| | | ├── Sidebar.jsx
| ├── details
| | ├── components
| | | ├── Form.jsx
| | | ├── ItemCard.jsx
Desde el principio, agrupe los componentes por ruta / módulo. Esta estructura permite el apoyo y la expansión a largo plazo. Esto evitará que la aplicación supere su arquitectura. Si confía en la "arquitectura de componentes de contenedor", esto sucederá muy rápidamente.
La arquitectura basada en módulos es altamente escalable. Simplemente agrega nuevos módulos sin aumentar la complejidad del sistema.
La "arquitectura de contenedores" no está mal, pero no es muy general (abstracta). No le dirá a nadie que lo aprenda, aparte de que usa React para desarrollar la aplicación.
Módulos comunes
Los componentes como botones, campos de entrada y tarjetas son omnipresentes. Incluso si no está utilizando un marco basado en componentes, extráigalos en componentes compartidos.
De esta manera, puede ver qué componentes comunes se utilizan en su aplicación, incluso sin la ayuda de un Storybook . Esto evita la duplicación de código. No quieres que cada miembro de tu equipo diseñe su propia versión del botón, ¿verdad? Desafortunadamente, esto suele deberse a una arquitectura de aplicación deficiente.
Caminos absolutos
Las partes individuales de la aplicación deben cambiarse lo más fácilmente posible. Esto se aplica no solo al código del componente, sino también a su ubicación. Las rutas absolutas significan que no tiene que cambiar nada cuando mueve el componente importado a una ubicación diferente. También facilita la localización del componente.
//
import Input from '../../../modules/common/components/Input'
//
import Input from '@modules/common/components/Input'
Utilizo el prefijo "@" como indicador del módulo interno, pero también he visto ejemplos del uso del carácter "~".
Envoltura de componentes externos
Intente no importar demasiados componentes de terceros directamente. Al crear un adaptador para dichos componentes, podemos modificar su API si es necesario. También podemos cambiar las bibliotecas utilizadas en un solo lugar.
Esto se aplica tanto a las bibliotecas de componentes como a las utilidades y la interfaz de usuario semántica. La forma más sencilla es volver a exportar dichos componentes desde el módulo compartido.
El componente no necesita saber qué biblioteca en particular estamos usando.
//
import { Button } from 'semantic-ui-react'
import DatePicker from 'react-datepicker'
//
import { Button, DatePicker } from '@modules/common/components'
Un componente, un directorio
Creo un directorio de componentes para cada módulo en mis aplicaciones. Primero, creo un componente. Luego, si hay necesidad de archivos adicionales relacionados con el componente, como estilos o pruebas, creo un directorio para el componente y coloco todos los archivos en él.
Es una buena práctica crear un archivo "index.js" para volver a exportar el componente. Esto le permite no cambiar las rutas de importación y evitar la duplicación del nombre del componente - "formulario de importación de 'componentes / UserForm / UserForm'". Sin embargo, no debe colocar el código del componente en el archivo "index.js", ya que esto hará imposible encontrar el componente por el nombre de la pestaña en el editor de código.
//
├── components
├── Header.jsx
├── Header.scss
├── Header.test.jsx
├── Footer.jsx
├── Footer.scss
├── Footer.test.jsx
//
├── components
├── Header
├── index.js
├── Header.jsx
├── Header.scss
├── Header.test.jsx
├── Footer
├── index.js
├── Footer.jsx
├── Footer.scss
├── Footer.test.jsx
Actuación
Optimización prematura
Antes de comenzar la optimización, asegúrese de que existan motivos para ello. Seguir ciegamente las mejores prácticas es una pérdida de tiempo si no tiene ningún impacto en la aplicación.
Por supuesto, debe pensar en cosas como la optimización, pero su preferencia debe ser desarrollar componentes legibles y mantenibles. El código bien escrito es más fácil de mejorar.
Si nota un problema de rendimiento de la aplicación, mida y determine la causa. No tiene sentido reducir la cantidad de re-renderizaciones con un tamaño de paquete enorme.
Después de identificar los problemas, corríjalos en orden de impacto en el rendimiento.
Tamaño de construcción
La cantidad de JavaScript que se envía al navegador es un factor clave en el rendimiento de la aplicación. La aplicación en sí puede ser muy rápida, pero nadie lo sabrá si tiene que precargar 4 MB de JavaScript para ejecutarla.
No apuntes a un solo paquete. Divida su aplicación a nivel de ruta y más. Asegúrese de enviar la cantidad mínima de código al navegador.
Cargar en segundo plano o cuando el usuario desee obtener otra parte de la aplicación. Si al hacer clic en un botón se inicia la descarga de un PDF, puede comenzar a descargar la biblioteca correspondiente en el momento en que coloque el cursor sobre el botón.
Re-renderizado: devoluciones de llamada, matrices y objetos
Debe esforzarse por reducir el número de repeticiones de renderizado de componentes. Tenga esto en cuenta, pero también tenga en cuenta que los renders innecesarios rara vez tienen un impacto significativo en la aplicación.
No envíe devoluciones de llamada como accesorios. Con este enfoque, la función se vuelve a crear cada vez, lo que activa la reproducción.
Si se encuentra con problemas de rendimiento causados por cierres, elimínelos. Pero no hagas que tu código sea menos legible o demasiado detallado.
Pasar matrices u objetos entra explícitamente en la misma categoría de problemas. Se comparan por referencia, por lo que no pasan una verificación superficial y provocan una repetición. Si necesita pasar una matriz estática, créelo como una constante antes de definir el componente. Esto permitirá que se pase la misma instancia cada vez.
Pruebas
Prueba de instantáneas
Una vez, me encontré con un problema interesante mientras hacía la prueba de instantáneas: comparar "nueva Fecha ()" sin un argumento con la fecha actual siempre devolvía "falso".
Además, las instantáneas solo dan como resultado ensamblajes fallidos cuando cambia el componente. Un flujo de trabajo típico es el siguiente: realice cambios en el componente, suspenda la prueba, actualice la instantánea y continúe.
Es importante comprender que las instantáneas no reemplazan las pruebas a nivel de componente. Personalmente, ya no uso este tipo de pruebas.
Prueba de la representación correcta
El objetivo principal de las pruebas es confirmar que el componente está funcionando como se esperaba. Asegúrese de que el componente devuelva el marcado correcto con los accesorios predeterminados y pasados.
Además, asegúrese de que la función siempre devuelva resultados correctos para entradas específicas. Compruebe que todo lo que necesita se muestra correctamente en la pantalla.
Prueba de estado y eventos
Un componente con estado generalmente cambia en respuesta a un evento. Cree un simulacro de evento y compruebe que el componente responde correctamente.
Asegúrese de que se llame a los controladores y se pasen los argumentos correctos. Compruebe la configuración correcta del estado interno.
Prueba de casos extremos
Después de cubrir el código con pruebas básicas, agregue algunas pruebas para verificar casos especiales.
Esto podría significar pasar una matriz vacía para asegurarse de que no se acceda al índice sin verificar. También podría significar llamar a un error en un componente (por ejemplo, en una solicitud de API) para verificar si se manejó correctamente.
Pruebas de integración
La prueba de integración significa probar una página completa o un componente grande. Este tipo de prueba significa probar el rendimiento de una cierta abstracción. Proporciona un resultado más convincente de que la aplicación está funcionando como se esperaba.
Los componentes individuales pueden pasar las pruebas unitarias con éxito, pero las interacciones entre ellos pueden causar problemas.
Estilización
CSS a JS
Este es un tema muy controvertido. Personalmente, prefiero usar bibliotecas como Styled Components o Emotion, que te permiten escribir estilos en JavaScript. Un archivo menos. No pienses en cosas como los nombres de las clases.
El bloque de construcción de React es un componente, por lo que la técnica CSS-in-JS, o más exactamente, todo en JS, es en mi opinión la técnica preferida.
Tenga en cuenta que otros enfoques de estilo (SCSS, módulos CSS, bibliotecas con estilos como Tailwind) no son incorrectos, pero aún así recomiendo usar CSS-in-JS.
Componentes estilizados
Por lo general, trato de mantener los componentes con estilo y el componente que los usa en el mismo archivo.
Sin embargo, cuando hay muchos componentes con estilo, tiene sentido moverlos a un archivo separado. He visto este enfoque utilizado en algunos proyectos de código abierto como Spectrum.
Recibiendo información
Bibliotecas para trabajar con datos
React no proporciona herramientas especiales para obtener o actualizar datos. Cada equipo crea su propia implementación, generalmente incluyendo un servicio para funciones asincrónicas que interactúan con la API.
Adoptar este enfoque significa que es nuestra responsabilidad rastrear el estado de la descarga y manejar los errores HTTP. Esto conduce a la verbosidad y mucho código repetitivo.
En su lugar, es mejor usar bibliotecas como React Query y SWR . Hacen de las interacciones con el servidor una parte orgánica del ciclo de vida de los componentes de una manera idiomática, utilizando ganchos.
Tienen soporte incorporado para almacenamiento en caché, administración de estado de carga y manejo de errores. También eliminan la necesidad de que las bibliotecas de gestión estatal manejen estos datos.
Gracias por su atención y buen comienzo de la semana laboral.