Unificação de componentes visuais. Parte 1. Estilos



Este artigo será útil principalmente para desenvolvedores que não trabalham com conjuntos de componentes prontos, como material-ui, mas implementam seus próprios. Por exemplo, um design foi desenvolvido para um produto que reflete como devem ser os botões, janelas modais etc. Para implementar corretamente esse sistema de design, todos os seus átomos precisarão adicionar um bom suporte para sua composição. Em outras palavras, é necessário garantir que qualquer componente único possa se integrar e se encaixar perfeitamente em um componente composto maior. E se ele não se encaixar, seria bom ter um suporte simples para sua personalização. Seja como for, este é um grande tópico separado, e talvez eu voltarei a ele outra vez.

Letra


Olá pessoal. Começo minha jornada no hub com um artigo simples, mas útil. Quanto a mim, ficou muito detalhado, mas ainda assim tentei me adaptar ao leitor, e não a mim mesmo. Antes de escrever o próximo artigo (se houver) sobre um tópico mais complexo, desejo ajustar minha apresentação ao feedback (se houver).

Termos utilizados:

  • Um componente visual é um componente que retorna um elemento incorporado no DOM. Por exemplo, return (<div />) . Um componente que retorna apenas outro componente não deve ser interpretado visualmente.

1. Introdução


Quando você desenvolve um componente, não pode torná-lo totalmente universal. Na sua cabeça, você sempre começa com opções específicas para o seu uso. Muitas vezes acontece que, após o desenvolvimento, seus colegas começam a "empurrar esse componente para qualquer lugar". Você está com raiva deles: “Bem, eu não desenvolvi isso para isso! Não se destina a esta tarefa! ” Obviamente, as melhorias são inevitáveis ​​e até necessárias. Mas isso não deve ser uma melhoria, como lançar novos adereços para aumentar o recuo de 4 para 8 pixels, que será usado em um ou dois casos em cinquenta. Os componentes devem ter geometria externa personalizada.

TypeScript, ajude


Considere a interface, que em significado deve estar localizada, por exemplo, em src/Library/Controls.ts . Breves comentários são feitos para os campos, abaixo os analisaremos com mais detalhes.

 export interface VisualComponentProps { //  .   - children?: React.ReactNode; //    css className?: string; //   ,     . doNotRender?: boolean; //     doNotRender true fallback?: JSX.Element; //    css style?: React.CSSProperties; } 

Essa é a interface de adereços do componente. Quais? Todos os componentes visuais. Eles devem ser aplicados ao seu elemento raiz.

Os adereços de cada componente visual desenvolvido devem ser expandidos usando essa interface.

Imediatamente, preste atenção ao fato de que todos esses acessórios são opcionais. Considere-os.

  • children estão nos componentes da classe React.Component, componentes do componente React.FC, mas não estão em funções comuns sem especificar a digitação do React.FC. Por isso, pedimos a ele.
  • className/style usamos nomes semelhantes, como no JSX'nom usual <div />. Nós não produzimos semântica. Esse princípio de identidade do nome é usado, por exemplo, em adereços para especificar o link ref .
  • doNotRender usado como uma alternativa à muleta dolorosa na renderização JSX por condição . Aplicando esta solução, não precisamos colocar chaves nos métodos de renderização, prejudicando a legibilidade do código. Compare dois trechos de código:

    Renderização condicional virgem:

    App.tsx

     renderComponent() { const {props, state} = this; const needRender = state.something; return ( <PageLayout> <UIButton children={'This is a button'} /> {needRender && <UIButton children={'This is another button'} /> } </PageLayout> ); } 

    Chad props doNotRender:

    App.tsx

     renderComponent() { const {props, state} = this; const needRender = state.something; return ( <PageLayout> <UIButton children={'This is a button'} /> <UIButton children={'This is another button'} doNotRender={!needRender} /> </PageLayout> ); } 

    Na primeira versão, aumentamos o nível de aninhamento do botão inferior, embora em termos de significado o aninhamento esteja no mesmo nível que o superior. Isso parece ruim no meu editor, onde uso uma guia com uma largura de 2 espaços, mas aqui é ainda pior.

    Na segunda opção, temos aninhamento igual, menos o doNotRender pode não chamar a atenção e o desenvolvedor não entenderá o que está acontecendo. Porém, se no seu projeto cada componente visual for criado de acordo com esse princípio, esse problema desaparecerá imediatamente.
  • fallback necessário se não queremos tornar null com doNotRender true , mas algum tipo de elemento personalizado. É usado por analogia com o React Suspense , pois tem um significado semelhante (não produzimos semântica)

Eu quero mostrar como usá-lo corretamente. Vamos fazer um botão simples.

Nota: no código abaixo também uso css-modules, sass e classnames.

UIButton.tsx

 import * as React from 'react'; import { VisualComponentProps } from 'Library/Controls'; import * as css from './Button.sass'; import cn from 'classnames'; //  ()  export interface ButtonBasicProps { disabled?: boolean; } export interface ButtonProps extends ButtonBasicProps, VisualComponentProps {} export function UIButton(props: ButtonProps) { //   undefined,     // "Nothing was returned from render." if (props.doNotRender) return props.fallback || null; //      const rootClassNames = cn( // ,   sass css.Button, // ,     props props.className, //      props.disabled && css._disabled ); return ( <div children={props.children} className={rootClassNames} style={props.style} /> ) } 

App.tsx

 renderComponent() { const {props, state} = this; const needRenderSecond = true; return ( <PageLayout> <UIButton children={'This is a button'} style={{marginRight: needRenderSecond ? 5 : null}} /> <UIButton disabled children={'This is another button'} doNotRender={!needRenderSecond} /> </PageLayout> ); } 

Resultado:



Reflexão e Conclusão


É conveniente operar com componentes como divs, criando vários invólucros, composições, especializações, indo além da estrutura de sua funcionalidade original .

Pode-se argumentar que, como não existem botões amarelos condicionais no sistema de design e o desenvolvedor precisa criá-los, o problema não está nos componentes, mas no fato de que essa necessidade cria. No entanto, a realidade é que essas situações surgem e com bastante frequência. "... Mas devemos viver! Devemos viver." Além disso, o princípio da cascata css nem sempre pode ser implementado na prática, e você pode experimentar casos em que seus estilos simplesmente se sobrepõem à maior especificidade de outro seletor (ou descrito acima). Aqui o estilo apenas ajuda.

Por fim, adicionarei alguns (literalmente) momentos.

  1. Observe que doNotRender não repete completamente o comportamento de renderização condicional. Você também executará métodos de ciclo de vida, apenas renderizar retornará um fallback ou nulo. Em alguns componentes complexos, convém evitar a execução de métodos de ciclo de vida. Para fazer isso, você só precisa fazer uma especialização interna do seu componente.

    Usando o exemplo UIButton: renomeie o UIButton para UIButtonInner e adicione o seguinte código:

    UIButton.tsx

     export function UIButton(props: ButtonProps) { if (props.doNotRender) return props.fallback || null; return <UIButtonInner {...props} />; } 

    PS Não cometa um erro de chamada UIButton recursiva nesta função!
  2. Em casos raros, quando os estilos no wrapper e no componente embrulhado podem mudar independentemente, a seguinte interface pode ser útil para você

    Library/Controls.ts

     export interface VisualComponentWrapperProps extends VisualComponentProps { wrappedVisual?: VisualComponentProps; } 

    E seu uso
    UIButton.tsx

     interface ButtonSomeWrapperProps extends ButtonBasicProps, VisualComponentWrapperProps { myCustomProp?: number; } export function UIButtonSomeWrapper(props: ButtonSomeWrapperProps) { if (props.doNotRender) return props.fallback || null; const { //  VisualComponentProps  style, className, children, fallback, doNotRender, // VisualComponentProps   wrappedVisual, //    myCustomProp, //     ...uiButtonProps } = props; return ( <div style={style} className={className} > {myCustomProp} <UIButton {...wrappedVisual} {...uiButtonProps} /> {children} </div> ); } 

O desenvolvimento de um aplicativo usando essa abordagem aumentará significativamente a reutilização de seus componentes, reduzirá o número de estilos desnecessários de muleta (falando sobre os estilos descritos nos arquivos de estilo do componente exclusivamente para as necessidades de outros componentes) e adereços, além de adicionar código estruturado. Isso é tudo. No próximo artigo, começaremos a resolver os problemas de reutilização de componentes mais em termos de código que em css. Ou vou escrever sobre algo mais interessante. Obrigado pela atenção.

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


All Articles