Não escreva a mesma coisa: como os componentes reutilizáveis ​​do React ajudarão os desenvolvedores front-end a criar aplicativos mais rapidamente


Fazer as mesmas alterações em três ou quatro lugares diferentes no código JS é uma arte que requer atenção. Se houver mais elementos, o suporte ao código se transforma em farinha. Portanto, para projetos de longo prazo ou grandes, você deve escrever um código para que ele possa ser executado em componentes separados.

Estou envolvido no desenvolvimento de front-end há 10 anos e falarei sobre o uso de componentes para criar elementos de front-end - isso simplifica bastante a vida de um desenvolvedor de front-end.

Escrito com suporte do Mail.ru Cloud Solutions .

O que são componentes de front-end e por que são necessários


Tags HTML - nível condicional "zero" de componentes. Cada um deles tem suas próprias funções e propósito.

As classes CSS são o próximo nível de abstração que geralmente é alcançado ao criar um site pequeno. Nas regras para aplicar estilos a uma classe CSS, descrevemos o comportamento de todos os elementos que fazem parte de um subconjunto condicional de elementos.

As regras aplicadas às classes CSS, bem como outros elementos, como tags HTML, permitem definir e alterar centralmente as regras para exibir qualquer número de elementos do mesmo tipo. Existem várias ferramentas para trabalhar com estilos de elementos - na verdade, CSS, Sass, LESS, PostCSS e metodologias para aplicar estilos - BEM, SMACSS, CSS atômico, módulos CSS, componentes com estilo.

Na verdade, os componentes são:

  • elementos do mesmo tipo que possuem os mesmos estilos e o mesmo layout (HTML) e comportamento (JS);
  • elementos de estilo e comportamento semelhantes que diferem um pouco do outro.

A tecnologia Web Components está sendo desenvolvida, o que permite criar tags HTML personalizadas e incluir trechos de código de modelo no layout. No entanto, os componentes tornaram-se amplamente utilizados graças às modernas estruturas de desenvolvimento front-end, como Angular, Vue, React. Os recursos de JavaScript facilitam a conexão de um componente:

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

Todos os principais projetos chegam à sua biblioteca de componentes prontos ou usam um dos já prontos. A questão de quando você precisa passar da cópia do código para a criação de componentes é decidida individualmente, não há receitas inequívocas.

Vale lembrar não apenas a escrita do código, mas também seu suporte. Uma cópia / colar simples do mesmo layout e estilos de isolamento nas classes CSS pode criar uma exibição por algum tempo, sem riscos específicos. Mas se uma lógica de comportamento escrita em JS for adicionada a cada elemento, os benefícios da reutilização do código serão sentidos literalmente de 2 a 3 elementos, especialmente quando se trata de dar suporte e modificar o código escrito anteriormente.

Componentes de reação reutilizáveis


Suponha que nosso aplicativo tenha se tornado grande o suficiente e decidimos escrever nossa própria biblioteca de componentes. Sugiro usar a popular ferramenta de desenvolvimento de front-end do React para isso. Uma de suas vantagens é a capacidade de usar de forma simples e eficiente componentes incorporados. No código abaixo, o componente mais antigo do aplicativo usa três componentes aninhados: 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}
               />
           </>
       )
   }
}

Observe : agora não é necessário usar a etiqueta de embrulho sênior no layout - normalmente era div. O Modern React oferece a ferramenta Fragment, cujo registro abreviado <></>. Dentro dessas tags, você pode usar uma hierarquia de tags simples, como no exemplo acima.

Utilizamos três componentes da biblioteca, um dos quais é duas vezes em um bloco. Os dados a partir da aplicação principal é transferida para os suportes do componente e estarão disponíveis dentro dele por meio da propriedade this.props. Essa abordagem é típica do React e permite montar rapidamente uma visualização a partir de elementos típicos. Especialmente se o seu aplicativo tiver muitas páginas semelhantes, que diferem apenas no conteúdo dos artigos (Modelo) e na funcionalidade.

No entanto, podemos precisar modificar o componente da biblioteca. Por exemplo, componentes com a mesma funcionalidade podem diferir não apenas no conteúdo textual, mas também no design: cor, recuos, bordas. Também é possível fornecer diferentes funcionalidades do mesmo componente.

O seguinte caso é considerado abaixo: dependendo da presença de um retorno de chamada, nosso componente pode ser "responsivo" ou simplesmente permanecer uma Visualização para renderizar um elemento na 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>
           }
        )
    }
} 

No React, existe outra técnica para expandir os recursos dos componentes. Nos parâmetros de chamada, você pode transferir não apenas dados ou retornos de chamada, mas também todo o layout:

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

O layout interno do componente será reproduzido na íntegra à medida que foi transferido para props.

Geralmente, é mais conveniente transferir layout adicional para o componente da biblioteca usando o padrão de inserção e uso this.props.children. Essa abordagem é melhor para modificar os componentes comuns responsáveis ​​pelos blocos típicos de um aplicativo ou site em que vários conteúdos internos são assumidos: limites, barras laterais, blocos com anúncios e outros.

// 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 completos do React


Os componentes que são responsáveis ​​apenas pelo View foram considerados acima. No entanto, provavelmente precisaremos enviar às bibliotecas não apenas o mapeamento, mas também a lógica padrão de processamento de dados.

Vejamos o componente Telefone, projetado para inserir um número de telefone. Ele pode mascarar o número digitado usando a biblioteca de validadores de plug-in e informar ao componente sênior que o telefone foi inserido corretamente ou incorretamente:

// 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 já possui um estado de estado interno, parte do qual pode compartilhar com o código externo que o chamou. A outra parte permanece dentro do componente, no exemplo acima onceValidated. Assim, parte da lógica do componente é completamente encerrada nele.

Pode-se dizer que o comportamento típico é independente de outras partes do aplicativo. Por exemplo, dependendo se o número foi validado ou não, podemos exibir diferentes prompts de texto. Colocamos em um componente reutilizável separado, não apenas a exibição, mas também a lógica de processamento de dados.

Componentes de MV


Se nosso componente padrão suportar funcionalidade avançada e possuir uma lógica de comportamento suficientemente desenvolvida, vale a pena dividi-lo em dois:

  • “Inteligente” para trabalhar com dados ( Model);
  • "Dumb" para exibir ( View).

A conexão continuará chamando um componente. Agora será Model. A segunda parte - View- será chamada render()com props, algumas das quais vieram do aplicativo e a outra parte já é o estado do próprio componente:

// 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 a pena prestar atenção na ferramenta React.forwardRef(). Permite criar refum componente Phone, mas vinculá-lo diretamente aos elementos de layout PhoneView. Todas as manipulações, como de costume ref, estarão disponíveis no Phone. Por exemplo, se precisarmos conectar um validador de número de telefone.

Outra característica dessa abordagem é a simplificação máxima do Viewcomponente. De fato, esta parte é definida como const, sem seus métodos internos. Somente layout e substituição de dados do modelo.

Agora, nosso componente reutilizável está dividido em Modele View, podemos desenvolver separadamente a lógica comercial e o código de layout. Também podemos montar o layout a partir de elementos de componentes ainda menores.

O estado de todo o aplicativo em execução nos componentes


Foi mostrado acima que o aplicativo pode gerenciar componentes passando parâmetros ou composição tipográfica e usando retornos de chamada.

Para que o aplicativo funcione com êxito, o nível superior precisa receber dados significativos sobre o estado dos componentes reutilizados aninhados. No entanto, esse pode não ser o nível mais alto de todo o aplicativo.

Se tivermos um bloco de autorização do cliente e componentes reutilizáveis ​​para inserir um login e senha, todo o aplicativo não precisará saber em que estado esses componentes simples estão em um dado momento. Em vez disso, o próprio bloco de autorização pode calcular um novo estado com base nos estados de componentes reutilizados simples e transmiti-lo: o bloco de autorização é preenchido corretamente ou não.

Com um grande aninhamento de componentes, é necessário monitorar a organização do trabalho com dados para saber sempre onde está a "fonte da verdade". Eu já escrevi

sobre algumas das dificuldades associadas à transição de estado assíncrona no React . Componentes reutilizáveis ​​sempre devem passar pelos retornos de chamada os dados necessários para gerenciar os possíveis blocos de componentes. No entanto, você não precisa transferir dados extras para não causar redesenhos desnecessários de grandes partes da árvore DOM e não complicar o código para processar alterações nos componentes. Outra abordagem para organizar dados é usar um contexto de chamada de componente. Este é um método React nativo. , disponível a partir da versão 16.3, para não confundir com a versão anterior ! ..



createContextReact getChildContext

Então você não precisa passar adereços pela "espessura" dos componentes na árvore de aninhamento de componentes. Ou use bibliotecas especializadas para gerenciamento de dados e entrega de alterações, como Redux e Mobx (consulte o artigo no pacote Mobx + React ).

Se criarmos uma biblioteca de componentes reutilizáveis ​​no Mobx, cada um desses tipos terá sua própria loja. Ou seja, a "fonte da verdade" sobre o estado de cada instância do componente, com acesso de ponta a ponta de qualquer lugar no aplicativo inteiro. No caso do Redux e seu único data warehouse, todos os estados de todos os componentes estarão disponíveis em um único local.

Algumas bibliotecas prontas de componentes React


Existem bibliotecas populares de componentes prontos, que, em regra, eram originalmente projetos 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 essas bibliotecas têm como objetivo criar layouts de elementos e seu estilo. A interação com o ambiente é configurada usando retornos de chamada. Portanto, se você deseja criar componentes reutilizáveis ​​completos descritos no terceiro e quarto parágrafos do artigo, precisará fazer isso sozinho. Talvez, tomando como componentes da Vista de tais componentes uma das bibliotecas populares apresentadas acima.

Este artigo foi escrito com o suporte do Mail.ru Cloud Solutions .


All Articles