Códigos limpios: Escribir componentes de React

códigos limpios

Consejos sencillos para escribir códigos limpios de React

En esta publicación, repasemos algunos consejos simples que lo ayudarán a escribir códigos limpios de React y escalar mejor su proyecto.

Códigos limpios


Evite pasar los accesorios con el operador de propagación.

Primero, comencemos con un antipatrón que debe evitar. A menos que haya una razón específica y justificada para hacerlo, debe evitar pasar accesorios por el árbol de componentes usando un operador de extensión, así: .

Pasar accesorios de esta manera hace que los componentes de escritura sean más rápidos. Sin embargo, también hace que sea muy difícil identificar los errores en su código. Pierde la confianza en sus componentes, lo que hace que sea más difícil refactorizarlos y, como resultado, los errores comenzarán a aparecer mucho antes.


Envuelva los parámetros de su función en un objeto

Si su función acepta varios parámetros, es una buena idea incluirlos en un objeto. Aquí tiene un ejemplo:

export const sampleFunction = ({ param1, param2, param3 }) => {
    console.log({ param1, param2, param3 });
}

Escribir la firma de su función de esta manera proporciona varias ventajas notables:

  1. Ya no necesita preocuparse por el orden en el que transmite sus argumentos. Cometí este error varias veces, donde introducía un error y pasaba argumentos de función en el orden incorrecto.
  2. Para los editores con IntelliSense configurado (la mayoría de ellos hoy en día), obtendrás una buena característica de autocompletar para tus argumentos de función.

Para los controladores de eventos, use funciones que devuelvan funciones de controlador

Si está familiarizado con la programación funcional, esta técnica se parece al curado, ya que está preconfigurando algunos de los parámetros con anticipación.

Echemos un vistazo al ejemplo:

import React from 'react'

export default function SampleComponent({ onValueChange }) {

    const handleChange = (key) => {
        return (e) => onValueChange(key, e.target.value)
    }


    return (
        <form>
            <input onChange={handleChange('name')} />
            <input onChange={handleChange('email')} />
            <input onChange={handleChange('phone')} />
        </form>
    )
}

Como puede ver, al escribir las funciones del controlador de esta manera, puede mantener más limpio el árbol de componentes.


Usa mapas sobre if / else

Cuando necesite renderizar diferentes elementos basados en la lógica personalizada, sugiero usar mapas sobre declaraciones if / else.

A continuación, se muestra un ejemplo con if / else:

import React from 'react'

const Student = ({ name }) => <p>Student name: {name}</p>
const Teacher = ({ name }) => <p>Teacher name: {name}</p>
const Guardian = ({ name }) => <p>Guardian name: {name}</p>


export default function SampleComponent({ user }) {
    let Component = Student;
    if (user.type === 'teacher') {
        Component = Teacher
    } else if (user.type === 'guardian') {
        Component = Guardian
    }

    return (
        <div>
            <Component name={user.name} />
        </div>
    )
}

Y aquí hay un ejemplo que usa mapas:

import React from 'react'

const Student = ({ name }) => <p>Student name: {name}</p>
const Teacher = ({ name }) => <p>Teacher name: {name}</p>
const Guardian = ({ name }) => <p>Guardian name: {name}</p>

const COMPONENT_MAP = {
    student: Student,
    teacher: Teacher,
    guardian: Guardian
}

export default function SampleComponent({ user }) {
    const Component = COMPONENT_MAP[user.type]

    return (
        <div>
            <Component name={user.name} />
        </div>
    )
}

Con esta simple estrategia, sus componentes se vuelven más declarativos y más fáciles de comprender. También hace que sea más sencillo extender la lógica y agregarle más elementos.


Componentes del gancho

Encuentro útil este patrón, siempre que no lo abuse.

Es posible que se encuentre utilizando algunos de los componentes en toda su aplicación. Si necesitan un estado para funcionar, puede envolverlos con un gancho que proporcione ese estado. Algunos buenos ejemplos de este tipo de componentes son las ventanas emergentes, las notificaciones tostadas o los modales simples. Por ejemplo, aquí hay un enlace de componente para un modal de confirmación simple:

import React, { useCallback, useState } from 'react';
import ConfirmationDialog from 'components/global/ConfirmationDialog';

export default function useConfirmationDialog({
    headerText,
    bodyText,
    confirmationButtonText,
    onConfirmClick,
}) {
    const [isOpen, setIsOpen] = useState(false);

    const onOpen = () => {
        setIsOpen(true);
    };

    const Dialog = useCallback(
        () => (
            <ConfirmationDialog
                headerText={headerText}
                bodyText={bodyText}
                isOpen={isOpen}
                onConfirmClick={onConfirmClick}
                onCancelClick={() => setIsOpen(false)}
                confirmationButtonText={confirmationButtonText}
            />
        ),
        [isOpen]
    );

    return {
        Dialog,
        onOpen,
    };
}

Entonces puede usar el gancho de su componente así:

import React from "react";
import { useConfirmationDialog } from './useConfirmationDialog'

function Client() {
  const { Dialog, onOpen } = useConfirmationDialog({
    headerText: "Delete this record?",
    bodyText:
      "Are you sure you want delete this record? This cannot be undone.",
    confirmationButtonText: "Delete",
    onConfirmClick: handleDeleteConfirm,
  });

  function handleDeleteConfirm() {
    //TODO: delete
  }

  const handleDeleteClick = () => {
    onOpen();
  };

  return (
    <div>
      <Dialog />
      <button onClick={handleDeleteClick} />
    </div>
  );
}

export default Client;

La abstracción del componente de esta manera le evita tener que escribir mucho código estándar de administración de estados.


División de componentes

Los siguientes tres consejos tratan sobre la división inteligente de sus componentes. Desde mi experiencia, mantener sus componentes pequeños es la mejor manera de mantener su proyecto manejable.


Use envoltorios

Si tiene dificultades para encontrar una manera de dividir su componente grande, observe la funcionalidad que ofrece cada elemento de su componente. Algunos elementos están ahí para proporcionar una funcionalidad distinta, como los controladores de arrastrar y soltar.

A continuación, se muestra un ejemplo de un componente que implementa la función de arrastrar y soltar mediante react-beautiful-dnd:

import React from 'react'
import { DragDropContext, Droppable } from 'react-beautiful-dnd';



export default function DraggableSample() {

    function handleDragStart(result) {
        console.log({ result });
    }

    function handleDragUpdate({ destination }) {
        console.log({ destination });
    }

    const handleDragEnd = ({ source, destination }) => {
        console.log({ source, destination });
    };

    return (
        <div>
            <DragDropContext
                onDragEnd={handleDragEnd}
                onDragStart={handleDragStart}
                onDragUpdate={handleDragUpdate}
            >
                <Droppable droppableId="droppable" direction="horizontal">
                    {(provided) => (
                        <div {...provided.droppableProps} ref={provided.innerRef}>
                            {columns.map((column, index) => {
                                return (
                                    <ColumnComponent
                                        key={index}
                                        column={column}
                                    />
                                );
                            })}
                        </div>
                    )}
                </Droppable>
            </DragDropContext>
        </div>
    )
}

Ahora revise el componente después de mover toda la lógica de arrastrar y soltar a un componente contenedor:

import React from 'react'

export default function DraggableSample() {
    return (
        <div>
            <DragWrapper>
                {columns.map((column, index) => {
                    return (
                        <ColumnComponent
                            key={index}
                            column={column}
                        />
                    );
                })}
            </DragWrapper>
        </div>
    )
}

Y aquí está el código del contenedor:

import React from 'react'
import { DragDropContext, Droppable } from 'react-beautiful-dnd';

export default function DragWrapper({children}) {

    function handleDragStart(result) {
        console.log({ result });
    }

    function handleDragUpdate({ destination }) {
        console.log({ destination });
    }

    const handleDragEnd = ({ source, destination }) => {
        console.log({ source, destination });
    };

    return (
        <DragDropContext
            onDragEnd={handleDragEnd}
            onDragStart={handleDragStart}
            onDragUpdate={handleDragUpdate}
        >
            <Droppable droppableId="droppable" direction="horizontal">
                {(provided) => (
                    <div {...provided.droppableProps} ref={provided.innerRef}>
                        {children}
                    </div>
                )}
            </Droppable>
        </DragDropContext>
    )
}

Como resultado, es más fácil echar un vistazo al componente y comprender lo que hace en un nivel alto. Toda la funcionalidad de arrastrar y soltar vive en el contenedor y es mucho más fácil razonar sobre ella.


Separación de intereses

En el contexto de React, la separación de preocupaciones significa separar las partes de los componentes que son responsables de buscar y mutar los datos y las responsables puramente de mostrar el árbol de elementos.

Este método de separación de preocupaciones es la razón principal por la que se introdujo el patrón de gancho. Puede y debe envolver toda la lógica que administra las API o las conexiones de estado global con un gancho personalizado.

Por ejemplo, echemos un vistazo a su componente:

import React from 'react'
import { someAPICall } from './API'
import ItemDisplay from './ItemDisplay'



export default function SampleComponent() {
    const [data, setData] = useState([])

    useEffect(() => {
        someAPICall().then((result) => {
            setData(result)
        })
    }, [])

    function handleDelete() {
        console.log('Delete!');
    }

    function handleAdd() {
        console.log('Add!');
    }

    const handleEdit = () => {
        console.log('Edit!');
    };

    return (
        <div>
            <div>
                {data.map(item => <ItemDisplay item={item} />)}
            </div>
            <div>
                <button onClick={handleDelete} />
                <button onClick={handleAdd} />
                <button onClick={handleEdit} />
            </div>
        </div>
    )
}

Ahora aquí está la versión refactorizada con el código dividido usando ganchos personalizados:

import React from 'react'
import ItemDisplay from './ItemDisplay'

export default function SampleComponent() {
    const { data, handleDelete, handleEdit, handleAdd } = useCustomHook()

    return (
        <div>
            <div>
                {data.map(item => <ItemDisplay item={item} />)}
            </div>
            <div>
                <button onClick={handleDelete} />
                <button onClick={handleAdd} />
                <button onClick={handleEdit} />
            </div>
        </div>
    )
}

Y aquí está el gancho en sí:

import { someAPICall } from './API'


export const useCustomHook = () => {
    const [data, setData] = useState([])

    useEffect(() => {
        someAPICall().then((result) => {
            setData(result)
        })
    }, [])

    function handleDelete() {
        console.log('Delete!');
    }

    function handleAdd() {
        console.log('Add!');
    }

    const handleEdit = () => {
        console.log('Edit!');
    };

    return { handleEdit, handleAdd, handleDelete, data }
}

Archivo separado para cada componente

A menudo, la gente escribe código como este:

import React from 'react'



export default function SampleComponent({ data }) {

    const ItemDisplay = ({ name, date }) => (
        <div>
            <h3>{name}</h3>
            <p>{date}</p>
        </div>
    )
    
    return (
        <div>
            <div>
                {data.map(item => <ItemDisplay item={item} />)}
            </div>

        </div>
    )
}

Si bien no hay nada terriblemente malo en escribir un componente de React de esta manera, no es una buena práctica a seguir. No hay desventajas en mover  a un archivo separado, y las ventajas son que sus componentes están débilmente acoplados y son más fáciles de extender.


Escribir códigos limpios en su mayor parte se reduce a ser consciente y tomarse el tiempo para seguir buenos patrones y evitar antipatrones. Entonces, si se toma el tiempo para seguir estos patrones, ayudará a sus componentes React más limpios de escritura.

¡Espero que pueda encontrar estos patrones muy útiles en sus proyectos!

Recent Post