No escriba lo mismo: cómo los componentes React reutilizados ayudarán a los desarrolladores front-end a crear aplicaciones más rápido


Hacer los mismos cambios en tres o cuatro lugares diferentes en el código JS es un arte que requiere atención. Si hay más elementos, el soporte de código se convierte en harina. Por lo tanto, para proyectos a largo plazo o grandes, debe escribir código para que pueda llevarse a cabo en componentes separados.

He estado involucrado en el desarrollo front-end durante 10 años y hablaré sobre el uso de componentes para crear elementos front-end. Esto simplifica enormemente la vida de un desarrollador front-end.

Escrito con el apoyo de Mail.ru Cloud Solutions .

¿Qué son los componentes frontend y por qué son necesarios?


Etiquetas HTML : condicionalmente el nivel "cero" de componentes. Cada uno de ellos tiene sus propias funciones y propósitos.

Las clases CSS son el siguiente nivel de abstracción que generalmente se alcanza al crear incluso un sitio pequeño. En las reglas para aplicar estilos a una clase CSS, describimos el comportamiento de todos los elementos que forman parte de un subconjunto condicional de elementos.

Las reglas que se aplican a las clases CSS, así como a cualquier otro elemento, como las etiquetas HTML, le permiten establecer y cambiar centralmente las reglas para mostrar cualquier número de elementos del mismo tipo. Existen varias herramientas para trabajar con estilos de elementos: en realidad CSS, Sass, LESS, PostCSS y metodologías para aplicar estilos: BEM, SMACSS, CSS atómico, módulos CSS, componentes con estilo.

En realidad los componentes son:

  • elementos del mismo tipo que tienen los mismos estilos y el mismo diseño (HTML) y comportamiento (JS);
  • elementos similares en estilo y comportamiento que difieren ligeramente entre sí.

Se está desarrollando la tecnología de componentes web, que le permite crear etiquetas HTML personalizadas e incluir piezas de código de plantilla en el diseño. Sin embargo, los componentes se han utilizado ampliamente gracias a los marcos de desarrollo front-end modernos como Angular, Vue, React. Las características de JavaScript facilitan la conexión de un componente:

import {Header, Footer} from "./components/common";
render() {
    return (
       ...
   )
}

Todos los proyectos importantes llegan a su biblioteca de componentes listos para usar o para usar uno de los listos. La cuestión de cuándo necesita pasar de copiar el código a crear componentes se decide individualmente, no hay recetas inequívocas.

Vale la pena recordar no solo la escritura del código, sino también su soporte. Una simple copia / pegado del mismo diseño y estilos de aislamiento en las clases CSS puede crear una pantalla durante algún tiempo sin ningún riesgo particular. Pero si se agrega una lógica de comportamiento escrita en JS a cada elemento, los beneficios de reutilizar el código se sienten literalmente de 2-3 elementos, especialmente cuando se trata de soportar y modificar el código escrito previamente.

Componentes de reacción reutilizables


Supongamos que nuestra aplicación se ha vuelto lo suficientemente grande y decidimos escribir nuestra propia biblioteca de componentes. Sugiero usar la popular herramienta de desarrollo front-end React para esto. Una de sus ventajas es la capacidad de utilizar de manera simple y eficiente los componentes integrados. En el siguiente código, el componente anterior de la aplicación utiliza tres componentes anidados: AppHeader, Article, AppFooter:

import React from "react";
import AppHeader from "./components/AppHeader";
import Article from "./components/Article";
import AppFooter from "./components/AppFooter";
export default class App extends React.Component {
    constructor(props) {
        super(props); 
        this.state = {
            title : "My App",
            contacts : "8 800 100 20 30"
           firtsArticleTitle : "Welcome",
           secondArticleTitle : "Let's speak about..."
        }
    };

    render() {
        return (
            <>
                <AppHeader 
                title={this.state.title}
            />
            <Article
                   title={this.state.firstArticleTitle}
               />
               <Article
                   title={this.state.secondArticleTitle}
               />               
               <AppFooter
                   contacts={this.state.contacts}
               />
           </>
       )
   }
}

Tenga en cuenta : ahora no es necesario usar la etiqueta de envoltura senior en el diseño, generalmente lo fue div. Modern React ofrece la herramienta Fragment, un registro abreviado del cual <></>. Dentro de estas etiquetas, puede usar una jerarquía de etiquetas planas, como en el ejemplo anterior.

Utilizamos tres componentes de la biblioteca, uno de los cuales es dos veces en un bloque. Los datos de la aplicación principal se transfieren a los accesorios del componente y estarán disponibles dentro de él a través de la propiedad this.props. Este enfoque es típico de React y le permite ensamblar rápidamente una vista a partir de elementos típicos. Especialmente si su aplicación tiene muchas páginas similares que difieren solo en el contenido de los artículos (Modelo) y la funcionalidad.

Sin embargo, es posible que necesitemos modificar el componente de la biblioteca. Por ejemplo, los componentes con la misma funcionalidad pueden diferir no solo en contenido textual, sino también en diseño: color, sangrías, bordes. También es posible proporcionar diferentes funcionalidades del mismo componente.

El siguiente caso se considera a continuación: dependiendo de la presencia de una devolución de llamada, nuestro componente puede ser "receptivo" o simplemente seguir siendo una Vista para representar un elemento en la página:

// App.js
...
render() {
    return (        
        <Article 
            text={this.state.articleText}
            onClick={(e) => this.bindTap(e)}
           customClass={this.state.mainCustomClass}
        />                
    )
}

// Article.js
import React from "react";

export default class Article extends React.Component {
    constructor(props) {
        super(props);         
    };

    render() {
       let cName="default";
       if (this.props.customClass) cName = cName + " " this.props.customClass;
       let bgColor="#fff";
       if (this.props.bgColor) bgColor = this.props.bgColor;
        return (
            {this.props.onClick &&
            <div
                   className={cName}
                onClick={(e) => this.props.onClick(e)}
                   style={{background : bgColor}}
            >
                <p>{this.props.text}<p/>
            </div>
            }
            {!this.props.onClick && 
                <div className={cName}>
                <p>{this.props.text}<p/>
                </div>
           }
        )
    }
} 

En React, hay otra técnica para expandir las capacidades de los componentes. En los parámetros de llamada, puede transferir no solo datos o devoluciones de llamada, sino también todo el diseño:

// App.js
...
render() {
    return (        
        <Article 
            title={this.state.articleTitle}
            text={
               <>
                <p>Please read the article</p>
                <p>Thirst of all, I should say programming React is a very good practice.</p>
               </>
            }
        />                
    )
}

// Article.js
import React from "react";
export default class Article extends React.Component {
    constructor(props) {
        super(props);         
    };

    render() {
        return (
            <div className="article">
            <h2>{this.props.title}</h2>
            {this.props.text}
            </div>
        )
    }
}

El diseño interno del componente se reproducirá en su totalidad a medida que se transfirió props.

A menudo es más conveniente transferir un diseño adicional al componente de la biblioteca utilizando el patrón de inserción y uso this.props.children. Este enfoque es mejor para modificar los componentes comunes responsables de los bloques típicos de una aplicación o sitio donde se asume diversos contenidos internos: mayúsculas, barras laterales, bloques con anuncios y otros.

// App.js
...
render() {
    return (        
        <Article title={this.state.articleTitle}>
           <p>Please read the article</p>
            <p>First of all, I should say programming React is a very good practice.</p>
       </Article>                          
    )
}

// Article.js
import React from "react";
export default class Article extends React.Component {
    constructor(props) {
        super(props);         
    };

    render() {
        return (
            <div className="article">
            <h2>{this.props.title}</h2>
            {this.props.children}
            </div>
        )
    }
} 

Componentes de reacción completos


Los componentes que son responsables solo de View se consideraron anteriormente. Sin embargo, lo más probable es que tengamos que enviar a las bibliotecas no solo la asignación, sino también la lógica de procesamiento de datos estándar.

Veamos el componente de teléfono, que está diseñado para ingresar un número de teléfono. Puede enmascarar el número ingresado utilizando la biblioteca de validación de complementos e informar al componente principal que el teléfono se ingresó correcta o incorrectamente:

// Phone.js
import React from "react";
import Validator from "../helpers/Validator";
export default class Phone extends React.Component {
    constructor(props) {
        super(props);   
        this.state = {
            value : this.props.value || "",
            name : this.props.name,
            onceValidated : false,
            isValid : false,
            isWrong : true
        }
        this.ref = React.createRef();    
    };

    componentDidMount = () => {
        this.setValidation();
    };

    setValidation = () => {
        const validationSuccess = (formattedValue) => {
            this.setState({
            value : formattedValue,
            isValid : true,
            isWrong : false,
            onceValidated : true
           });
            this.props.setPhoneValue({
            value : formattedValue, 
            item : this.state.name, 
            isValid : true
            })
        }
        const validationFail = (formattedValue) => {
            this.setState({
            value : formattedValue,
            isValid : false,
            isWrong : true,
            });
            this.props.setPhoneValue({
            value : formattedValue, 
            item : this.state.name, 
            isValid : false
            })
        }
        new Validator({
            element : this.ref.current,
            callbacks : {
            success : validationSuccess,
            fail : validationFail
            }
        });
    }

    render() {
        return (
            <div className="form-group">
            <labeL htmlFor={this.props.name}>
                    <input 
                name={this.props.name}
                id={this.props.name}
                type="tel"
                placeholder={this.props.placeholder}
                defaultValue={this.state.value}
                ref={this.ref}
                />
            </label>
            </div>
        )
    }

} 

Este componente ya tiene un estado de estado interno, una parte del cual puede compartir con el código externo que lo llamó. La otra parte permanece dentro del componente, en el ejemplo anterior onceValidated. Por lo tanto, parte de la lógica del componente está completamente encerrada en él.

Se puede decir que el comportamiento típico es independiente de otras partes de la aplicación. Por ejemplo, dependiendo de si el número fue validado o no, podemos mostrar diferentes mensajes de texto. Tomamos un componente reutilizable separado no solo de la pantalla, sino también de la lógica de procesamiento de datos.

Componentes MV


Si nuestro componente estándar admite una funcionalidad avanzada y tiene una lógica de comportamiento suficientemente desarrollada, entonces vale la pena dividirlo en dos:

  • "Inteligente" para trabajar con datos ( Model);
  • "Dumb" para mostrar ( View).

La conexión continuará llamando a un componente. Ahora lo será Model. La segunda parte - Viewse llamará render()con props, algunos de los cuales provienen de la aplicación, y la otra parte ya es el estado del componente en sí:

// App.js
...
render() {
    return (        
        <Phone 
            name={this.state.mobilePhoneName}
            placeholder={"You mobile phone"}
        />                
    )
}

// Phone.js
import React from "react";
import Validator from "../helpers/Validator";
import PhoneView from "./PhoneView";
export default class Phone extends React.Component {
    constructor(props) {
        super(props);   
        this.state = {
            value : this.props.value || "",
            name : this.props.name,
            onceValidated : false,
            isValid : false,
            isWrong : true
        }
        this.ref = React.createRef();    
    };

    componentDidMount = () => {
        this.setValidation();
    };

    setValidation = () => {
        const validationSuccess = (formattedValue) => {
            ...
        }
        const validationFail = (formattedValue) => {
            ...
        }
        new Validator({
           element : this.ref.current,
            ...
        });
    }    

   render() {
        return (
            <PhoneView
                name={this.props.name}
            placeholder={this.props.placeholder}
               value={this.state.value}
               ref={this.ref}
            />
        )
    }
}

// PhoneView.js
import React from "react";
const PhoneView = React.forwardRef((props, ref) => (   
    <div className="form-group">
        <labeL htmlFor={props.name}>
            <input 
                name={props.name}
            id={props.name}
            type="tel"
            ref={ref}
            placeholder={props.placeholder}
            value={props.value}                
            />
        </label>
    </div>    
));
export default PhoneView;

Vale la pena prestar atención a la herramienta React.forwardRef(). Le permite crear refen un componente Phone, pero vincularlo directamente a elementos de diseño en PhoneView. Todas las manipulaciones como de costumbre refestarán disponibles en Phone. Por ejemplo, si necesitamos conectar un validador de número de teléfono.

Otra característica de este enfoque es la máxima simplificación del Viewcomponente. De hecho, esta parte se define como const, sin sus métodos integrados. Solo diseño y sustitución de datos del modelo.

Ahora nuestro componente reutilizable se divide en Modely View, podemos desarrollar por separado la lógica empresarial y el código de diseño. También podemos ensamblar diseños a partir de elementos componentes aún más pequeños.

El estado de toda la aplicación que se ejecuta en componentes


Se mostró anteriormente que la aplicación puede administrar componentes pasando parámetros o composición tipográfica, y utilizando devoluciones de llamada.

Para que la aplicación funcione correctamente, el nivel superior necesita recibir datos significativos sobre el estado de los componentes reutilizados anidados. Sin embargo, este puede no ser el nivel más alto de toda la aplicación.

Si tenemos un bloque de autorización del cliente y componentes reutilizables para ingresar un nombre de usuario y contraseña, la aplicación completa no necesita saber en qué estado se encuentran estos componentes simples en un momento dado. Más bien, el bloque de autorización en sí mismo puede calcular un nuevo estado basado en los estados de componentes simples reutilizados y pasarlo: el bloque de autorización se llena correctamente o no.

Con una gran anidación de componentes, es necesario monitorear la organización del trabajo con datos para saber siempre dónde está la "fuente de verdad". Ya he escrito

sobre algunas de las dificultades asociadas con la transición de estado asíncrono en React . Los componentes reutilizables siempre deben pasar por las devoluciones de llamada los datos necesarios para gestionar los posibles bloques de componentes. Sin embargo, no es necesario que transfiera datos adicionales para no volver a dibujar innecesariamente grandes partes del árbol DOM y no complicar el código para procesar cambios en los componentes. Otro enfoque para organizar datos es usar un contexto de invocación de componentes. Este es un método nativo de React. , disponible a partir de la versión 16.3, ¡no debe confundirse con una anterior ! ..



createContextReact getChildContext

Entonces no tiene que pasar accesorios a través del "grosor" de los componentes por el árbol de anidación de componentes. O utilice bibliotecas especializadas para la gestión de datos y la entrega de cambios, como Redux y Mobx (consulte el artículo sobre el paquete Mobx + React ).

Si creamos una biblioteca de componentes reutilizables en Mobx, cada uno de los tipos de dichos componentes tendrá su propia Tienda. Es decir, la "fuente de la verdad" sobre el estado de cada instancia del componente, con acceso de extremo a extremo desde cualquier lugar de la aplicación completa. En el caso de Redux y su único almacén de datos, todos los estados de todos los componentes estarán disponibles en un solo lugar.

Algunas bibliotecas listas para usar de componentes React


Hay bibliotecas populares de componentes listos para usar, que, por regla general, fueron originalmente proyectos internos de empresas:

  1. Material-UI — , Material Design Google.
  2. React-Bootstrap — , . : API , , .
  3. VKUI — «». VK mini apps, (. VK mini apps). VKUI «». «» . vkconnect — iOS Android.
  4. ARUI Feather — React- -. , . open source, .

Todas estas bibliotecas están destinadas a crear diseños de elementos y su estilo. La interacción con el entorno se configura mediante devoluciones de llamada. Por lo tanto, si desea crear componentes reutilizables completos descritos en los párrafos tercero y cuarto del artículo, deberá hacerlo usted mismo. Quizás, tomando como componentes de la Vista de dichos componentes, una de las bibliotecas populares presentadas anteriormente.

Este artículo fue escrito con el apoyo de Mail.ru Cloud Solutions .


All Articles