A complexidade dos sites modernos cresceu significativamente nos últimos anos. A crescente demanda por designs de alta qualidade e padrão da indústria intensifica ainda mais os desafios enfrentados pelos desenvolvedores de front-end.
Hoje, até mesmo aplicativos de front-end precisam de algumas considerações arquitetônicas para simplificar o processo de desenvolvimento. Em meu artigo anterior , compartilhei minha experiência na implementação da abordagem de arquitetura limpa em aplicativos front-end enquanto trabalhava em meu projeto paralelo .
Neste artigo, pretendo me aprofundar na abordagem do design atômico, com base em minhas experiências com o mesmo projeto. Discutirei suas vantagens e desvantagens e avaliarei sua utilidade em diferentes cenários.
Para começar, vamos explorar o conceito de um sistema de design. Os sistemas de design são coleções abrangentes de componentes, diretrizes e princípios reutilizáveis que capacitam as equipes a projetar e desenvolver interfaces de usuário consistentes em várias plataformas.
Eles atuam como uma única fonte de verdade para designers e desenvolvedores, garantindo que os aspectos visuais e funcionais de um produto se alinhem e sigam a identidade da marca estabelecida. Se você estiver interessado em explorar exemplos de implementações de sistemas de design, considere examinar o seguinte:
Se você quiser mergulhar mais fundo no tópico de sistemas de design, recomendo verificar este artigo . Ele descreve este tópico em detalhes, detalhes que não são necessários para nós no escopo deste trabalho.
Com base nos sistemas de design, o design atômico é uma metodologia que agiliza a organização e a estruturação de componentes e diretrizes reutilizáveis. Concebido por Brad Frost, o Atomic Design é inspirado na química, pois desconstrói as interfaces do usuário em seus blocos de construção mais fundamentais e os remonta em estruturas mais intrincadas.
Aqui está uma imagem que ilustra a analogia com a química:
As reações químicas são representadas por equações químicas, que geralmente mostram como os elementos atômicos se combinam para formar moléculas. No exemplo acima, vemos como o hidrogênio e o oxigênio se combinam para formar moléculas de água.
Em essência, o design atômico é uma evolução natural dos sistemas de design, fornecendo uma abordagem sistemática para a criação de componentes flexíveis e escaláveis. Ao aplicar os princípios do design atômico, as equipes podem gerenciar seus sistemas de design com mais eficiência, pois a natureza modular dessa metodologia facilita a manutenção, atualização e extensão dos componentes e padrões do sistema.
Se você está preocupado que isso possa parecer complexo, não se preocupe. Nas próximas seções, demonstrarei como aplicar esses princípios usando um exemplo da vida real do aplicativo que desenvolvi, tornando-o fácil de entender e implementar em seus próprios projetos.
O design atômico organiza os componentes em cinco níveis distintos, cada um baseado no anterior. Vamos explorar esses cinco níveis em detalhes:
átomos : os blocos de construção mais básicos de uma interface de usuário, os átomos representam elementos HTML individuais, como botões, campos de entrada e cabeçalhos. Eles são as menores unidades funcionais e não podem ser mais divididos.
moléculas : as moléculas são formadas pela combinação de dois ou mais átomos em um grupo funcional. Por exemplo, uma molécula de formulário de pesquisa pode consistir em um átomo de entrada de pesquisa, um átomo de botão e um átomo de rótulo. As moléculas representam componentes simples que podem ser reutilizados em um projeto.
Organismos : os organismos são componentes mais complexos, criados pela combinação de múltiplas moléculas e/ou átomos. Eles representam seções distintas de uma interface de usuário, como cabeçalho, rodapé ou barra lateral. Os organismos ajudam a formar o layout geral e a estrutura de uma página.
templates : os templates são essencialmente layouts de página construídos usando organismos, moléculas e átomos. Eles definem a estrutura e a disposição dos componentes em uma página sem especificar nenhum conteúdo real, servindo como modelo para vários cenários de conteúdo.
páginas : as páginas são as instâncias finais e totalmente realizadas de modelos, completas com conteúdo e dados reais. Eles representam o que os usuários verão e interagirão, mostrando como os componentes e o layout se adaptam a diferentes tipos de conteúdo e casos de uso.
A fim de desenvolver uma perspectiva bem informada sobre design atômico para desenvolvimento de front-end, embarquei em uma jornada para criar um aplicativo. Durante um período de seis meses, ganhei informações e experiências valiosas enquanto trabalhava neste projeto.
Consequentemente, os exemplos fornecidos ao longo deste artigo são extraídos de minha experiência prática com o aplicativo. Para manter a transparência, todos os exemplos são derivados de código acessível publicamente.
Você pode explorar o resultado final visitando o repositório ou o próprio site .
Lembre-se de que usarei exemplos codificados em React . Se você não estiver familiarizado com essa linguagem, não se preocupe - pretendo ilustrar os conceitos fundamentais do design atômico, em vez de focar nos detalhes essenciais do código.
Para entender melhor os componentes do meu repositório, você pode encontrá-los no seguinte diretório: /client/presentation
. Nesse local, criei um novo diretório chamado atoms
para manter a nomenclatura consistente com a metodologia de design atômico. Este novo diretório contém todas as pequenas partes necessárias para construir todo o processo de integração.
A lista completa de átomos é a seguinte:
atoms ├── box ├── button ├── card ├── card-body ├── card-footer ├── container ├── divider ├── flex ├── form-control ├── form-error-message ├── form-helper-text ├── form-label ├── heading ├── icon ├── input ├── list ├── list-icon ├── list-item ├── spinner ├── tab ├── tab-list ├── tab-panel ├── tab-panels ├── tabs └── text
Esses nomes de átomos podem ser familiares para você, pois são baseados no pacote Chakra UI . A maioria deles já contém o estilo de correspondência padrão para meu aplicativo, portanto, não há nada particularmente exclusivo para descrever neste nível. Com isso em mente, podemos prosseguir diretamente para discutir as molecules
.
Nesta fase, o processo de design atômico torna-se mais interessante e seu verdadeiro poder começa a se revelar. Embora definir seus átomos básicos possa ter sido uma tarefa demorada e monótona, construir novos componentes usando átomos se torna muito mais agradável.
Para definir as moléculas, criei um diretório molecules
dentro do meu diretório /client/presentation
. A lista completa de moléculas necessárias é a seguinte:
molecules ├── available-notion-database ├── full-screen-loader ├── input-control ├── onboarding-step-layout └── onboarding-tab-list
De fato, com apenas cinco moléculas, temos componentes suficientes para atingir nosso objetivo. É importante observar que este também é um local ideal para incluir layouts compartilhados construídos sobre outros átomos. Por exemplo, o onboarding-step-layout
é utilizado para manter uma aparência consistente em todas as cinco etapas do processo de integração.
Os outros componentes são os seguintes:
available-notion-database : Usado para exibir os detalhes do banco de dados do usuário buscado (os usuários podem ter vários bancos de dados, então ofereço a capacidade de escolher um na etapa 4).
O componente aparece na interface do usuário assim:
import { FC } from 'react'; import { Flex, Spinner } from '@presentation/atoms'; import { FullScreenLoaderProps } from './full-screen-loader.types'; export const FullScreenLoader: FC<FullScreenLoaderProps> = ({ children, ...restProps }): JSX.Element => ( <Flex alignItems="center" bg="gray.50" height="full" justifyContent="center" left={0} position="fixed" top={0} width="full" zIndex="9999" {...restProps} > <Spinner /> {children} </Flex> );
Não há ciência de foguetes aqui. Esta é apenas uma combinação dos já definidos átomos flex
e spinner
.
input
com form-label
, form-control
, form-error-label
e spinner
para mostrar se há alguma ação em segundo plano acontecendo. O componente aparece na interface do usuário assim:
Agora que mais peças estão prontas, podemos definir blocos maiores em nosso quebra-cabeça de design.
Esta seção é onde crio cada componente responsável por exibir cada etapa do processo de integração.
Para esclarecer as coisas, mostrarei apenas a lista de organismos criados:
organisms ├── onboarding-step-one ├── onboarding-step-two ├── onboarding-step-three ├── onboarding-step-four └── onboarding-step-five
Acredito que os nomes são auto-explicativos e não deve haver mal-entendidos. Para ilustrar como montei tudo, vou apresentar o código de um passo como exemplo. Claro, se você quiser conferir mais, basta visitar meu repositório .
export const OnboardingStepFour: FC<OnboardingStepFourProps> = ({ onBackButtonClick, onNextButtonClick, }): JSX.Element => { const { hasApiTokenData, isSetApiTokenLoading, setApiToken, setApiTokenError } = useSetApiToken(); const handleInputChange = debounce(async (event: ChangeEvent<HTMLInputElement>) => { const result = await setApiToken(event.target.value); if (result) { onNextButtonClick(); } }, 1000); return ( <OnboardingStepLayout subtitle="Paste your copied integration token below to validate your integration." title="Validate your integration" onBackButtonClick={onBackButtonClick} > <InputControl isRequired errorMessage={setApiTokenError || undefined} isDisabled={isSetApiTokenLoading || hasApiTokenData} isLoading={isSetApiTokenLoading} label="Integration token" name="integrationToken" placeholder="Your integration token" onChange={handleInputChange} /> </OnboardingStepLayout> ); };
Este código é totalmente responsável por exibir a etapa quatro do meu processo de integração. Acredito que a única preocupação que você possa ter é em fazer requisições em organismos. Isso é aceitável? Não existe uma resposta única para todos, e preciso responder a essas preocupações com "Depende". Depende da sua estrutura.
Se incluir uma chamada de API em uma molécula ou organismo faz sentido no contexto de seu aplicativo e não complica excessivamente o componente, pode ser uma solução aceitável. Apenas tome cuidado para não permitir que os componentes da apresentação fiquem muito ligados à busca de dados ou à lógica de negócios, pois isso pode torná-los mais difíceis de manter e testar.
No meu cenário, esse componente é usado em um local e outras soluções para realizar uma chamada de API nesse cenário são mais complexas e podem produzir muito mais código do que o necessário.
Nesse estágio, o foco está na estrutura e no arranjo dos componentes, e não nos detalhes mais sutis da IU. Os modelos também ajudam a identificar onde o gerenciamento de estado deve residir, geralmente nos componentes da página que usam os modelos.
No exemplo de código fornecido, temos um componente Onboarding
que serve como modelo:
import { FC } from 'react'; import { Flex, Heading, TabPanels, Tabs, Text } from '@presentation/atoms'; import { OnboardingTabList } from '@presentation/molecules'; import { OnboardingStepFive, OnboardingStepFour, OnboardingStepOne, OnboardingStepThree, OnboardingStepTwo, } from '@presentation/organisms'; import { OnboardingProps } from './onboarding.types'; export const Onboarding: FC<OnboardingProps> = ({ activeTabs, createNotionIntegrationTabRef, displayCreateNotionIntegrationTab, displaySelectNotionDatabaseTab, displayShareDatabaseIntegrationTab, displayValidateIntegrationTab, displayVerifyDatabaseTab, selectNotionDatabaseTabRef, shareDatabaseIntegrationTabRef, validateIntegrationTabRef, verifyDatabaseTabRef, }) => ( <Flex direction="column" overflowX="hidden" px={2} py={{ base: '20px', sm: '25px', md: '55px' }}> <Flex direction="column" textAlign="center"> <Heading color="gray.700" fontSize={{ base: 'xl', sm: '2xl', md: '3xl', lg: '4xl' }} fontWeight="bold" mb="8px" > Configure your Notion integration </Heading> <Text withBalancer color="gray.400" fontWeight="normal"> This information will let us know from which Notion database we should use to get your vocabulary. </Text> </Flex> <Tabs isLazy display="flex" flexDirection="column" mt={{ base: '10px', sm: '25px', md: '35px' }} variant="unstyled" > <OnboardingTabList activeTabs={activeTabs} createNotionIntegrationTabRef={createNotionIntegrationTabRef} selectNotionDatabaseTabRef={selectNotionDatabaseTabRef} shareDatabaseIntegrationTabRef={shareDatabaseIntegrationTabRef} validateIntegrationTabRef={validateIntegrationTabRef} verifyDatabaseTabRef={verifyDatabaseTabRef} /> <TabPanels maxW={{ md: '90%', lg: '100%' }} mt={{ base: '10px', md: '24px' }} mx="auto"> <OnboardingStepOne onNextButtonClick={displayCreateNotionIntegrationTab} /> <OnboardingStepTwo onBackButtonClick={displayVerifyDatabaseTab} onNextButtonClick={displayShareDatabaseIntegrationTab} /> <OnboardingStepThree onBackButtonClick={displayCreateNotionIntegrationTab} onNextButtonClick={displayValidateIntegrationTab} /> {activeTabs.validateIntegration ? ( <OnboardingStepFour onBackButtonClick={displayShareDatabaseIntegrationTab} onNextButtonClick={displaySelectNotionDatabaseTab} /> ) : null} {activeTabs.selectNotionDatabase ? ( <OnboardingStepFive onBackButtonClick={displayVerifyDatabaseTab} /> ) : null} </TabPanels> </Tabs> </Flex> );
Esse componente Onboarding
reúne átomos, moléculas e organismos para criar o layout do processo de integração. Observe que o gerenciamento de estado e a lógica de navegação de guias foram separados desse componente. As funções necessárias de estado e retorno de chamada agora são recebidas como props, permitindo que um componente de "página" de nível superior manipule o estado e o gerenciamento de dados.
Essa separação de preocupações mantém o modelo focado no layout e na estrutura, ao mesmo tempo em que garante que o gerenciamento de estado seja tratado no nível apropriado.
No final, gostaria de apresentar o passo 4 como resultado final:
No contexto de nossa discussão anterior, o componente "page" usa o modelo Onboarding
e lida com o gerenciamento de estado para o processo de integração. Embora o código desse componente de página específico não seja fornecido aqui, você pode encontrá-lo em meu repositório. Conforme mencionado, não há nada de extraordinário no código do componente da página; ele se concentra principalmente em gerenciar o estado e transmiti-lo ao modelo Onboarding
.
Se dermos uma olhada em como o design atômico se parece na prática. Vamos mergulhar nos prós e contras dessa abordagem.
Embora o design atômico ofereça inúmeros benefícios óbvios, como modularidade, reutilização e manutenção, ele também apresenta algumas desvantagens que valem a pena considerar no início:
configuração inicial e complexidade : o design atômico requer uma estrutura e organização bem planejadas, que podem ser demoradas e difíceis de configurar inicialmente. Ele também pode apresentar complexidade adicional à sua base de código, especialmente para projetos menores, nos quais essa abordagem granular pode ser desnecessária.
curva de aprendizado : para desenvolvedores novos no design atômico, a metodologia pode ter uma curva de aprendizado acentuada. Requer uma compreensão sólida dos diferentes níveis e como eles se encaixam, o que pode ser esmagador para iniciantes.
sobrecarga : a implementação do design atômico pode envolver a criação de um grande número de componentes pequenos e especializados. Isso pode levar ao aumento da sobrecarga no gerenciamento e manutenção desses componentes, principalmente quando um componente é usado apenas em um contexto específico.
risco de excesso de engenharia : com o foco na criação de componentes reutilizáveis e modulares, há um risco potencial de excesso de engenharia, onde os desenvolvedores podem gastar muito tempo refinando componentes individuais em vez de se concentrar no aplicativo mais amplo.
comunicação e colaboração : o sucesso do design atômico depende de comunicação e colaboração claras entre designers, desenvolvedores e outras partes interessadas. Deixar de estabelecer uma linguagem ou entendimento comum da metodologia pode levar a confusão e inconsistências na implementação.
No entanto, essa abordagem tem seus próprios pontos fortes já mencionados. Vamos falar sobre eles com mais detalhes:
escalabilidade : ao decompor o design nos elementos mais fundamentais, a construção da complexidade dos componentes tornou-se uma tarefa mais gerenciável. Embora criar átomos apresentasse alguns desafios, criar qualquer componente baseado nesses átomos era extremamente agradável.
eficiência : a capacidade de reutilizar átomos, moléculas e organismos reduz significativamente o tempo gasto na concepção e desenvolvimento de novos recursos. Uma vez estabelecidos os componentes básicos, criar novas interfaces pode ser tão simples quanto combinar elementos existentes.
consistência : vem diretamente do ponto anterior. Como os mesmos átomos, moléculas e organismos são usados em vários modelos e páginas, a interface do usuário permanece uniforme, proporcionando uma experiência perfeita para os usuários.
documentação : o design atômico suporta inerentemente a documentação. A estrutura baseada em átomos pode servir como um guia visual claro de como os componentes devem ser construídos e usados. Isso pode ser especialmente útil para integrar novos membros da equipe.
sustentabilidade : um dos maiores pontos fortes do design atômico é como ele contribui para a sustentabilidade de um sistema de design. Ao quebrar tudo em suas partes atômicas, quaisquer alterações ou atualizações podem ser feitas no nível atômico e então propagadas pelo sistema. Por exemplo, se você decidir mudar a cor de um botão, você só precisa fazer essa mudança uma vez no nível do átomo, e isso será refletido em todas as moléculas, organismos e modelos onde esse botão é usado. Isso simplifica muito o processo de atualização e manutenção do sistema de design ao longo do tempo.
Em conclusão, embora o design atômico possa parecer uma faca de dois gumes - um pouco assustador em termos de configuração inicial e curva de aprendizado - seus benefícios potenciais valem a luta inicial. E lembre-se, mesmo as espadas mais formidáveis são inofensivas nas mãos de um cavaleiro habilidoso!