Executando verificação de segurança...
17

React: Component PATTERN que você precisa conhecer

Você já viu a lib Headless UI? É uma lib que dispobibiliza componentes como dialog (modal), listbox (select) não estilizados e com acessibilidade. Mas esse post NÃO é sobre isso. Uma coisa muito interessante sobre essa lib (e não é a única que usa esse pattern, muito pelo contrário), é a forma como são os seus componentes são usados:

...
<Dialog>
  <Dialog.Panel>
    <Dialog.Title>
        ...
    </Dialog.Title>
    
    <Dialog.Description>
        ...
    </Dialog.Description>

    ...

  </Dialog.Panel>
</Dialog>
...

Se você usar só o <Dialog> ele é como um componente, mas se você usar o <Dialog.Panel> ele é um outro componente completamente diferente, assim como seu <Dialog.Title> e <Dialog.Description>.

Então a questão é, como você pode usar esse pattern para seus próprios componentes?

Como usar esse PATTERN?

Pensando em um exemplo mais prático de aplicar, vamos de uma lista, que é composta de <ul> e <li>, o resultado que queremos alcançar é o componente para ser usado da seguinte forma:

<List>
    <List.Item>...</List.Item>
    <List.Item>...</List.Item>
    <List.Item>...</List.Item>
</List>

Vamos começar pelo <List>

Um componente List teria a seguinte estrutura:

function List({ children }) {
    return (
        <ul>
            {children}
        </ul>
    )
}

export { List };

Depois o <Item>

No mesmo arquivo...

...
function Item({ children }) {
    return (
        <li>
            {children}
        </li>
    )
}
...

E como criar o <List.Item>?

E a resposta é o Object. Mais precisamente o Object.assign.

Ele tem diversas utilidades, e eu recomendo dar uma pesquisada melhor sobre ele, mas o uso dele que vamos usar aqui é pra criar um objeto que possui um "default" que é o List e valor que é o Item.

Também vamos mudar o nome do List para Main (esse Main é só um padrão que gosto de usar mas ele pode ser usado com outro nome).

function Main({ children }) {
   return (
       <ul>
           {children}
       </ul>
   )
}

function Item({ children }) {
   return (
       <li>
           {children}
       </li>
   )
}

const List = Object.assign(Main, {
   Item, // Se tiver mais componentes é só ir incluindo aqui
});

export { List }

Tenho usado esse PATTERN a alguns meses e é ótimo para criar componentes, principalmente se estiver desenvolvendo um Design System, então comenta aí o que achou. Já conhecia esse pattern? Vai começar usar?

Autor: Dan Sousa

Carregando publicação patrocinada...
3
1

Se a única maneira de exportação é pelo List, sim.

Mas isso também é como exportar vários componentes e criar um index.ts para usar o seguinte:

export * as List from './List';
1

Mas dessa forma perderia o componente default do List, que é o Main. O que o Object.assign permite é ao mesmo tempo ter o default pra List sendo Main e também o valor Item.

Caso fosse dessa forma com export sem o Object.assign, o uso do List seria <List.Main>.

1

Não, você pode usar só o List, nesse exemplo do artigo o seguinte código funcionaria sem o List.Item:

<List>
   <li>...</li>
</List>

Mas um ponto é que o código do <List.Item> estaria no bundle mesmo não sendo usado. Então essa abordagem é mais interessante para componetes em que você usa todas as opções em conjunto.

3
3
2

É muito interessante mesmo, é um pattern usado pelo Headless UI do Tailwind e tb pelo Radix UI, bom mesmo, deixa bem descritivo o componente inteiro e suas partes.

2

Sempre ouvi falarem que esse tipo de pattern se encaixa muito bem no React Native, mas nem tanto no ReactJS, em questões de boas praticas e/ou cultura dentro de uma empresa, o que vocês acham??

4

Eu acho bem relativo. Acredito muito, e uso, na premissa de que devemos usar aquilo que funciona melhor na equipe, eu estou testando essa pattern aqui no projeto da firma e a galera ta amando. pois dá pra deixar bem organizado os componentes e pra quem tem fobia de mil imports no esse é um bom jeito de resolver.

Quando eu utilizava Styled Components eu usava uma estratégia bem parecida com esse pattern:

import * as S from './styles'

function Component() {
  return (
    <S.Heading>Hello World!</S.Heading>
  )
}
2
2

Não sei se entendi sua pergunta, patterns são literalmente padrões de programação, talvez você utilize um pattern e ainda não tenha se dado conta, mesmo que indiretamente.

No geral, não é necessário requisitos minímos para se utilizar padrões de programação. Você implementando o dp desse post já é uma forma de iniciar.

1

Ótima colocação do tiagoCali, completamentado a resposta dele, esse pattern é como se pensar em clean code, quando se está apredendo a programar, não é necessário se preocupar, a princípio, se o código está limpo mas sim sobre seu funcionamento.

Quando vai ganhando mais senioridade é importante pensar em patterns para garantir um código "readable" (legível), manutenível, performático e etc...

Aí que entra patterns e princípios como SOLID, por exemplo. Mas é importante ir praticando desde já. 😉

2

Ótimo post, confesso que nunca tinha visto esse pattern mas já consigo ver problemas que eu tive e que seriam resolvidos se usasse esse pattern, como conflitos de imports de mesmo nome e afins. Mais uma opção pra ter no futuro.

2
2

Muito interessante esse Design Pattern.
Eu já utilizei dessa forma conforme aprendi no curo React Avançado do Willian Justen.
Ele utiliza:

export * as S from './styles';

Dessa forma pra colocar os componentes visuais na tela fica assim:

<S.Container>
    <S.Title>Título da página</S.Title>
</S.Container>

Eu acho muito legal essa forma pois não precisamos ficar trocando de aba pra ver o nome que a gente deu pra um determinado estilo.

1
1

Não. Nesse caso estamos falando de estilos de um componente só.
Por exemplo: Temos o componente
no styles.ts dele temos

import {styled} from 'styled-components'

const List = styled.ul`
    background: red;
`;

const Item = styled.li``

e no index.tsx teremos:

import * as S from './styles.ts'

return (
    <S.List>
        <S.Item>item 1</S.Item>
        <S.Item>item 2</S.Item>
    </S.List>
)

Confira o projeto implementado com esse PATTERN aqui: Projeto do curso REACT AVANÇADO - WILLIAN JUSTEN

2

Eu já tinha visto essa prática em algumas libs antes, mas até então não imaginei que era algo como uma convensão de design pattern, muito interessante. O próprio Primer (lib do DS do GitHub, usado pelo TabNews) usa esse DP. Bom artigo introdutório para o assunto! 🙂

1
1

Eu acho esse padrão muito interessante, mas eu sempre fico pensando: Isso não deixaria o bundle final mais pesado? Precisaria de usar alguma prática de tree-shaking pra retirar os componentes que não vão ser usados desses objetos? Afinal, se você está carregando um único objeto com enésimos componentes, você consequentemente leva todos esses componentes, mesmo que você não esteja usando eles 🤔

Essa dúvida é a única coisa que me impede de começar a desenvolver nesse formato

1

Mas isso é bom para utilizar em componentes que possuam vínculo entre si.

Por exemplo, nesse caso do artigo, se você for importar o List, muito provavelmente você utilizará o List.Item junto. E é válido ao contrário também.

Ou seja, se você importar um, consequentemente terá que importar o outro também!

1