Pequenos componentes: o que poderia dar errado? Usamos o princípio da responsabilidade exclusiva

Apresentamos a sua atenção a tradução de um artigo de Scott Domes, publicado no blog.bitsrc.io. Descubra no kat por que os componentes devem ser tão pequenos quanto possível e como o princípio da responsabilidade exclusiva afeta a qualidade das aplicações.


Foto Austin Kirk com Unsplash

A vantagem do sistema de componentes React (e bibliotecas semelhantes) é que sua interface do usuário é dividida em pequenas partes que são fáceis de ler e reutilizáveis.

Esses componentes são compactos (100-200 linhas), o que permite que outros desenvolvedores os entendam e modifiquem facilmente.

Embora os componentes, em regra, tentem ser mais curtos, não há uma restrição clara e estrita no seu comprimento. O React não se importará se você decidir encaixar seu aplicativo em um componente assustadoramente grande, composto por 3.000 linhas.

... mas não vale a pena. A maioria dos seus componentes, provavelmente, já é muito volumosa - ou melhor, eles executam muitas funções.

Neste artigo, vou provar que a maioria dos componentes (mesmo com o comprimento normal de 200 linhas) deve ter um alvo mais restrito. Eles devem executar apenas uma função e executá-la bem. É isso que Eddie Osmani diz muito bem aqui .

Dica : ao trabalhar em JS, use Bit para organizar, montar e reutilizar componentes como peças lego. Bit é uma ferramenta extremamente eficaz para esse negócio, ele ajudará você e sua equipe a economizar tempo e acelerar a montagem. Apenas tente.

Vamos demonstrar como, ao criar componentes , algo pode dar errado .

Nosso aplicativo


Imagine que temos um aplicativo padrão para blogueiros. E aqui está o que na tela principal:

class Main extends React.Component { render() { return ( <div> <header> // Header JSX </header> <aside id="header"> // Sidebar JSX </aside> <div id="post-container"> {this.state.posts.map(post => { return ( <div className="post"> // Post JSX </div> ); })} </div> </div> ); } } 

(Este exemplo, como muitos outros subsequentes, deve ser considerado como pseudocódigo.)

Ele exibe o painel superior, a barra lateral e a lista de postagens. Tudo é simples.

Como também precisamos baixar postagens, podemos fazer isso enquanto o componente está sendo montado:

 class Main extends React.Component { state = { posts: [] }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } render() { // Render code } } 

Também temos alguma lógica pela qual a barra lateral é chamada. Se o usuário clicar no botão no painel superior, o lado será fechado. Você pode fechá-lo de cima e do próprio painel lateral.

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } handleOpenSidebar() { // Open sidebar by changing state } handleCloseSidebar() { // Close sidebar by changing state } render() { // Render code } } 

Nosso componente se tornou um pouco mais complicado, mas ainda fácil de ler.

Pode-se argumentar que todas as suas partes servem a um propósito: exibir a página principal do aplicativo. Então, seguimos o princípio da responsabilidade exclusiva.

O princípio da responsabilidade exclusiva afirma que um componente deve cumprir apenas uma função. Se reformularmos a definição retirada de wikipedia.org , verifica-se que cada componente deve ser responsável por apenas uma parte da funcionalidade [aplicativo].

Nosso componente principal atende a esse requisito. Qual é o problema?

Aqui está uma formulação diferente do princípio: qualquer [componente] deve ter apenas um motivo para a mudança .

Esta definição é retirada do livro de Robert Martin , Rapid Software Development. Princípios, exemplos, prática ” e é de grande importância.

Ao focar em um motivo para alterar nossos componentes, podemos criar aplicativos melhores que, além disso, serão fáceis de configurar.

Para maior clareza, vamos complicar nosso componente.

Complicação


Suponha que um mês após a implementação do componente Principal, um novo recurso tenha sido atribuído ao desenvolvedor por nossa equipe. Agora, o usuário poderá ocultar uma postagem (por exemplo, se ela contiver conteúdo inapropriado).

Isso não é difícil de fazer!

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [] }; // older methods get filteredPosts() { // Return posts in state, without the postsToHide } render() { return ( <div> <header> // Header JSX </header> <aside id="header"> // Sidebar JSX </aside> <div id="post-container"> {this.filteredPosts.map(post => { return ( <div className="post"> // Post JSX </div> ); })} </div> </div> ); } } 

Nosso colega lidou facilmente com isso. Ela adicionou apenas um novo método e uma nova propriedade. Nenhum dos que examinou a pequena lista de mudanças teve objeções.

Algumas semanas depois, outro recurso é anunciado - uma barra lateral aprimorada para a versão móvel. Em vez de mexer com CSS, o desenvolvedor decide criar vários componentes JSX que serão executados apenas em dispositivos móveis.

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [], isMobileSidebarOpen: false }; // older methods handleOpenSidebar() { if (this.isMobile()) { this.openMobileSidebar(); } else { this.openSidebar(); } } openSidebar() { // Open regular sidebar } openMobileSidebar() { // Open mobile sidebar } isMobile() { // Check if mobile device } render() { // Render method } } 

Outra pequena mudança. Alguns novos métodos bem nomeados e uma nova propriedade.

E aqui temos um problema. Main ainda executa apenas uma função (renderizando a tela principal), mas você vê todos esses métodos com os quais estamos lidando agora:

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [], isMobileSidebarOpen: false }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } handleOpenSidebar() { // Check if mobile then open relevant sidebar } handleCloseSidebar() { // Close both sidebars } openSidebar() { // Open regular sidebar } openMobileSidebar() { // Open mobile sidebar } isMobile() { // Check if mobile device } get filteredPosts() { // Return posts in state, without the postsToHide } render() { // Render method } } 

Nosso componente se torna grande e volumoso, é difícil de entender. E com a expansão da funcionalidade, a situação só vai piorar.

O que deu errado?

Única razão


Vamos voltar à definição do princípio de responsabilidade exclusiva: qualquer componente deve ter apenas um motivo para a mudança .

Anteriormente, alteramos a maneira como as postagens são exibidas, portanto tivemos que alterar nosso componente Principal. Em seguida, alteramos a maneira como a barra lateral se abre - e novamente alteramos o componente Principal.

Este componente tem muitos motivos não relacionados à mudança. Isso significa que ele executa muitas funções .

Em outras palavras, se você puder alterar significativamente uma parte do seu componente e isso não levar a alterações na outra parte, o componente terá muita responsabilidade.

Separação mais eficiente


A solução é simples: você precisa dividir o componente Principal em várias partes. Como fazer isso?

Vamos começar de novo. A renderização da tela principal permanece de responsabilidade do componente Principal, mas a reduzimos apenas para exibir os componentes relacionados:

 class Main extends React.Component { render() { return ( <Layout> <PostList /> </Layout> ); } } 

Ótimo

Se mudarmos repentinamente o layout da tela principal (por exemplo, adicionar seções adicionais), então Principal também será alterado. Em outros casos, não teremos motivos para tocá-lo. Ótimo.

Vamos para o Layout :

 class Layout extends React.Component { render() { return ( <SidebarDisplay> {(isSidebarOpen, toggleSidebar) => ( <div> <Header openSidebar={toggleSidebar} /> <Sidebar isOpen={isSidebarOpen} close={toggleSidebar} /> </div> )} </SidebarDisplay> ); } } 

Isso é um pouco mais complicado. Layout é responsável por renderizar os componentes do layout (painel lateral / painel superior). Mas não sucumbiremos à tentação e daremos ao Layout responsabilidade de determinar se a barra lateral está aberta ou não.

Atribuímos essa função ao componente SidebarDisplay , que transmite os métodos ou estados necessários aos componentes Header e Sidebar .

(O exemplo acima é um exemplo do padrão Render Props via Children no React. Se você não estiver familiarizado com ele, não se preocupe. É importante que exista um componente separado que controle o estado aberto / fechado da barra lateral.)

E então, a própria Sidebar pode ser bastante simples se for responsável por renderizar a barra lateral à direita.

 class Sidebar extends React.Component { isMobile() { // Check if mobile } render() { if (this.isMobile()) { return <MobileSidebar />; } else { return <DesktopSidebar />; } } } 

Novamente, resistimos à tentação de inserir JSX para computadores / dispositivos móveis diretamente nesse componente, pois nesse caso, haverá dois motivos para a mudança.

Vejamos outro componente:

 class PostList extends React.Component { state = { postsToHide: [] } filterPosts(posts) { // Show posts, minus hidden ones } hidePost(post) { // Save hidden post to state } render() { return ( <PostLoader> { posts => this.filterPosts(posts).map(post => <Post />) } </PostLoader> ) } } 

PostList muda apenas se PostList a maneira como a lista de postagens é PostList . Parece óbvio, certo? É exatamente disso que precisamos.

PostLoader muda apenas se PostLoader a maneira como as postagens são carregadas. E, finalmente, a Post muda apenas se alterarmos a maneira como a postagem é renderizada.

Conclusão


Todos esses componentes são pequenos e executam uma função pequena. As razões para as alterações nelas são fáceis de identificar e os próprios componentes são testados e corrigidos.

Agora, nossa aplicação é muito mais fácil de modificar: reorganize componentes, adicione novas e expanda a funcionalidade existente. Você só precisa olhar para um arquivo de componente para determinar para que serve.

Sabemos que nossos componentes mudarão e crescerão com o tempo, mas a aplicação dessa regra universal ajudará a evitar dívidas técnicas e a aumentar a velocidade da equipe. Você decide como distribuir os componentes, mas lembre-se - deve haver apenas um motivo para alterar o componente .

Obrigado por sua atenção e aguardamos seus comentários!

Source: https://habr.com/ru/post/pt437504/


All Articles