Enjoy 20% off all plans by following us on social media. Check out other promotions!
Guia de Entrevista Front End

Princípios de Design de API para Componentes de Interface do Usuário

Melhores práticas para projetar APIs de componentes de interface do desenvolvedor, úteis para entrevistas de codificação de componentes de IU e design de sistemas

Bibliotecas de componentes de interface de usuário como Bootstrap e Material UI ajudam os desenvolvedores a criar UI mais rapidamente, fornecendo componentes comumente usados, como botões, guias, modais, etc, para que os desenvolvedores não precisem reinventar a roda construindo esses componentes do zero sempre que iniciarem um novo projeto.

Frequentemente, durante entrevistas de front-end, você será solicitado a construir componentes de IU e projetar uma API para inicializá-los. Designing good component APIs is bread and butter for Front End Engineers. This page covers some of the top tips and best practices for designing UI component APIs. Algumas dessas dicas podem ser específicas para determinados frameworks, mas podem ser generalizadas para outros frameworks de interface de usuário baseados em componentes.

Inicialização

jQuery-style

Antes das modernas bibliotecas de IU JavaScript como React, Angular, e Vue aparecerem, [jQuery](https://jquery. om/) (e jQuery UI) era a maneira mais popular de construir a interface do usuário. A jQuery UI popularizou a ideia de inicializar os componentes da UI através de "construtores" que envolvia dois argumentos:

  1. Elemento Raiz: Um elemento raiz da DOM para renderizar o conteúdo.
  2. Opções de Customização: Opcionais, opções adicionais de customização, geralmente na forma de um objeto JavaScript simples.

Usando o jQuery UI, é possível transformar um elemento DOM em um slider (entre muitos outros componentes de UI) com uma única linha de código:

<div id="gfe-slider"></div>
<script>
$('#gfe-slider').slider();
</script>

jQuery refresher: o método slider() do jQuery UI (construtor) recebe um objeto JavaScript que serve como opções de personalização. Ao fazer $('#slider') seleciona-se o elemento <div id="slider"> e retorna um objeto jQuery que contém métodos convenientes para "fazer algo" com o elemento como addClass, removeClass, etc e outros métodos de manipulação da DOM. Dentro dos métodos do jQuery, o elemento selecionado pode ser acessado através da palavra-chave this. as APIs do jQuery são construídas em torno dessa abordagem de "selecionar um elemento e fazer algo com ele", portanto o método slider() não precisa de um argumento para o elemento raiz da DOM.

O slider pode ser customizado passando um objeto JavaScript simples com opções:

<div id="gfe-slider"></div>
<script>
$('#gfe-slider').liderar({
animate: true,
max: 50,
min: 10,
// Veja outras opções aqui: https://api. queryui.com/slider/
});
</script>

Estilo JavaScript Puro

Não existe um estilo de JavaScript puro para inicializar componentes, uma vez que JavaScript puro não é um padrão ou framework. Mas se você já leu as soluções do GreatFrontEnd, para nossas perguntas sobre codificação de UI, você verá que a API que recomendamos é semelhante ao do jQuery, o construtor recebe um elemento raiz e opções:

function slider(rootEl, opções) {
// Faça algo com rootEl e as opções.
}

React

O React te obriga a escrever a interface de usuário como componentes, que contêm sua própria lógica e aparência. Componentes do React são funções JavaScript que retornam marcações (uma descrição de como renderizar a si mesmos). Componentes do React podem receber props, que são essencialmente personalizações das opções de um componente.

function Slider({ min, max }) {
// Use as props para renderizar um componente personalizado.
return <div>...</div>;
}
<Slider max={50} min={10} />;

Componentes não recebem um elemento raiz. Para renderizar o elemento na página, uma API separada é utilizada.

import { createRoot } from 'react-dom/client';
import Slider from './Slider';
const domNode = document.getElementById('#gfe-slider');
// O React vai gerenciar o DOM dentro deste elemento.
const root = createRoot(domNode);
// Exibe o componente Slider dentro do elemento.
root.render(<Slider max={50} min={10} />);

Geralmente, você não precisará chamar createRoot() manualmente se toda a página for uma aplicação React, pois haverá apenas uma chamada de createRoot para o componente raiz/nível da página.

Customizando a Aparência

Embora os componentes de interface de usuário em bibliotecas de UI forneçam estilos padrão, os desenvolvedores geralmente desejam personalizá-los com a identidade visual e as cores de tema de sua empresa/produto. Portanto, todos os componentes de interface de usuário permitirão a personalização da aparência, por meio de alguns métodos:

Injeção de classe

A ideia aqui é simples, os componentes aceitam uma prop/opção para permitir que o desenvolvedor forneça suas próprias classes, e essas classes são adicionadas aos elementos DOM reais. Essa abordagem não é muito robusta, pois se o componente também adicionar seus próprios estilos por meio de classes, pode haver propriedades conflitantes entre as classes do componente e as classes fornecidas pelo desenvolvedor.

React

import clsx from 'clsx';
function Slider({ className, value }) {
return (
<div className={clsx('gfe-slider', className)}>
<input type="range" value={value} />
</div>
);
}
<Slider className="my-custom-slider" value={50} />;
/* UI library default stylesheet */
.gfe-slider {
height: 12px;
}
/* Developer's custom stylesheet */
.my-custom-slider {
color: red;
}

Através da injeção de classes, os desenvolvedores podem alterar a cor do texto do componente para ser "vermelha".

Se houver muitos elementos DOM dentro do componente a serem direcionados e uma única propriedade className não for suficiente, você também pode ter várias propriedades com nomes diferentes para as classes de diferentes elementos:

import { useId } from 'react';
import clsx from 'clsx';
function Slider({ label, value, className, classNameLabel, classNameTrack }) {
const id = useId();
return (
<div className={clsx('gfe-slider', className)}>
<label className={clsx('gfe-slider-label', classNameLabel)} for={id}>
{label}
</label>
<input
className={clsx('gfe-slider-range', classNameRange)}
id={id}
type="range"
value={value}
/>
</div>
);
}

jQuery

No jQuery, classes também podem ser passadas como um campo nas opções.

$('#gfe-slider').slider({
// Na verdade, o jQuery UI recebe um campo `classes` em vez disso
// já que existem vários elementos.
class: 'my-custom-slider',
});

Na realidade, todos os inicializadores de componentes do jQuery UI recebem o campo classes para permitir a adição de classes adicionais a elementos individuais. O exemplo a seguir é retirado dejQuery UI Slider:

$('#gfe-slider').slider({
classes: {
'ui-slider': 'highlight',
'ui-slider-handle': 'ui-corner-all',
'ui-slider-range': 'ui-corner-all ui-widget-header',
},
});

Estilização não determinística

A injeção de classes tem uma desvantagem não óbvia — o resultado visual final é não-determinístico e pode não ser o esperado. Considere o seguinte código como exemplo:

import clsx from 'clsx';
function Slider({ className, value }) {
return (
<div className={clsx('gfe-slider', className)}>
<input type="range" value={value} />
</div>
);
}
<Slider className="my-custom-slider" value={50} />;
/* UI library default stylesheet */
.gfe-slider {
height: 12px;
color: black;
}
/* Folha de estilos customizada do desenvolvedor */
.my-custom-slider {
color: red; /* .gfe-slider também define um valor para a cor. */
}

No exemplo acima, tanto as classes .gfe-slider quanto .my-custom-slider especificam o color, e como esses dois seletores têm a mesma especificidade, o estilo vencedor é na verdade a classe que aparece mais tarde na página HTML. Se a ordem de carregamento da folha de estilos não for garantida (por exemplo, se as folhas de estilos forem carregadas de forma preguiçosa), o resultado visual não será determinístico. É nesse momento que os desenvolvedores começam a usar "hacks" como !important ou .my-custom-slider.my-custom-slider para fazer com que seus seletores vençam a guerra de especificidade, e o código CSS começa a se tornar difícil de manter.

No jQuery UI, se uma classe personalizada for adicionada, o valor padrão existente não será utilizado. Isso elimina a ambiguidade do "estilo vencedor", mas o usuário agora precisa reimplementar todos os estilos necessários presentes na classe original. Essa abordagem também pode ser aplicada a componentes do React para resolver a ambiguidade.

Apesar de suas possíveis falhas, a injeção de classes ainda é uma opção muito popular.

Hooks de Seletores CSS

Tecnicamente falando, os desenvolvedores podem alcançar a personalização se lerem o código-fonte do componente e definirem seus estilos personalizados usando as mesmas classes. No entanto, fazer isso é perigoso, pois depende das partes internas de um componente e não há garantia de que os nomes das classes não mudarão no futuro.

Se os autores de bibliotecas de UI puderem tornar essas classes/atributos parte de sua API, que oferece essas garantias:

  1. A lista de seletores é publicada para referência externa.
  2. Os seletores publicados existentes não serão alterados. Se forem alterados, será uma mudança que quebra a compatibilidade e será necessário fazer um incremento de versão de acordo com o semver.

Nesse caso, é uma prática aceitável e os desenvolvedores podem "conectar-se" a eles (selecioná-los) usando esses seletores em suas folhas de estilo.

Um exemplo de conexão com seletores de um componente:

import { useId } from 'react';
import clsx from 'clsx';
function Slider({ label, value }) {
const id = useId();
return (
<div className="gfe-slider">
<label className="gfe-slider-label" for={id}>
{label}
</label>
<input className="gfe-slider-range" id={id} type="range" value={value} />
</div>
);
}
/* Biblioteca de UI padrão de estilo */
. fe-slider {
font-size: 12px;
}
/* Nenhuma outra classe é definida nesta folha de estilos,
gfe-slider-label e gfe-slider-range são adicionados
ao componente apenas para que os desenvolvedores tenham acesso
aos elementos subjacentes. */
/* Folha de estilo personalizada do desenvolvedor */
.gfe-slider {
font-size: 16px; /* Conflitos com o padrão .gfe-slider */
padding: 10px 20px;
}
.gfe-slider-label {
color: red;
}
.gfe-slider-range {
height: 20px;
}

Essa abordagem poupa os desenvolvedores do trabalho de passar classes para o componente, pois eles só precisam escrever CSS para personalizar o estilo. Reach UI, uma biblioteca de componentes de UI para React, usa seletores de elementos. Cada componente possui um atributo `data-reach-* no elemento DOM subjacente.

[data-reach-menu-item] {
color: blue;
}

No entanto, essa abordagem ainda sofre com o problema de estilização não determinística, como na "injeção de classes", e não permite facilmente a estilização por instância. Se a estilização por instância for desejada, essa abordagem pode ser combinada com a abordagem de injeção de classes.

Objeto de Tema

Em vez de receber classes, o componente recebe um objeto de chaves/valores para estilização. Isso é útil se houver apenas um subconjunto restrito de propriedades para personalizar ou se você quiser restringir a estilização a apenas algumas propriedades.

const defaultTheme = { color: 'black', height: 12 };
function Slider({ value, label, theme }) {
// Combinar com o padrão.
const finalTheme = { ...defaultTheme, ...theme };
return (
<div className="gfe-slider">
<label
for={id}
style={{
color: finalTheme.color,
}}>
{label}
</label>
<input
id={id}
type="range"
value={value}
style={{
height: finalTheme.height,
}}
/>
</div>
);
}
<Slider themeOptions={{ color: 'red', height: 24 }} {...props} />;

No entanto, como não são usadas classes com estilos conflitantes e os estilos em linha têm maior especificidade do que as classes, não há conflito de especificidade e os estilos em linha serão aplicados. No entanto, o número de opções que precisam ser suportadas pode aumentar rapidamente. Os estilos em linha também estão presentes no DOM por instância do componente, o que pode ser prejudicial para o desempenho se esse componente for renderizado centenas/milhares de vezes em uma página.

O objeto de tema é apenas uma maneira de restringir a estilização a determinadas propriedades e, opcionalmente, a um conjunto aceito de valores. Os valores não precisam ser usados como estilos em linha e podem ser combinados com outras abordagens de estilização.

CSS Preprocessor Compilation

Bibliotecas de UI geralmente são escritas com pré-processadores de CSS como [Sass] (https://sass-lang.com/) e Less. Bootstrap é escrito com Sass e eles fornecem uma maneira de [personalizar as variáveis Sas](https://getbootstrap. om/docs/5.3/customize/sass/) usados para que os desenvolvedores possam gerar uma biblioteca de biblioteca de UI personalizada.

Essa abordagem é excelente porque não depende de substituir seletores CSS para alcançar a personalização. Também há uma quantidade menor de CSS resultante e nenhum estilo redundante sobreposto. A desvantagem é que é necessário um passo de compilação.

Variáveis CSS / Propriedades Personalizadas

[Variáveis CSS](https://developer.mozilla. rg/en-US/docs/Web/CSS/Using_CSS_custom_properties) (ou mais formalmente conhecido como propriedades personalizadas CSS) são entidades definidas por autores de CSS que contêm valores específicos para serem reutilizados através de um documento. A função var(), aceita valores de fallback se a variável determinada não estiver definida.

function Slider({ value, label }) {
return (
<div className="gfe-slider">
<label for={id}>{label}</label>
<input id={id} type="range" value={value} />
</div>
);
}
/* UI library default stylesheet */
.gfe-slider {
/*Reserva de 12px caso não seja definido. */
font-size: var(--gfe-slider-font-size, 12px);
}
/* Folha de estilo personalizada do desenvolvedor */
:root {
--gfe-slider-font-size: 15px;
}

O desenvolvedor pode definir um valor para --gfe-slider-font-size globalmente através do seletor :root e definir o tamanho da fonte para a classe .gfe-slider como 15px. O benefício dessa abordagem é que ela não requer JavaScript, no entanto, a personalização por componente será mais trabalhosa (mas ainda possível).

Render Props

No React, as "render props" são props de função que um componente usa para saber o que renderizar. Isso é útil para separar comportamento da apresentação. Muitas bibliotecas de UI comportamentais/headless como Radix, Headless UI e Reach UI fazem amplo uso de "render props".

Internacionalização (i18n)

Minha interface de usuário funciona para múltiplos idiomas? Quão fácil é adicionar suporte para mais idiomas?

Evitar codificação rígida de rótulos em um determinado idioma

Alguns componentes de interface de usuário possuem strings de rótulo dentro deles (por exemplo, um carrossel de imagens possui rótulos para os botões anterior/próximo). Seria bom permitir a personalização dessas strings de rótulo tornando-as parte das props/opções do componente.

Idiomas da direita para a esquerda

Alguns idiomas (como árabe, hebraico) são lidos da direita para a esquerda, e a interface do usuário precisa ser espelhada horizontalmente. O componente pode pegar uma prop/opção direction e alterar a ordem de como os elementos são renderizados. Por exemplo, os botões de anterior e próximo estarão à direita e à esquerda, respectivamente, em um idioma da direita para a esquerda (RTL).

Use propriedades lógicas CSS para tornar seus estilos mais à prova de futuro e permitir que seu layout funcione para diferentes modos de escrita.