Composição do componente no React JS

Após 2 anos de trabalho com React, tenho algumas experiências que gostaria de compartilhar. Se você acabou de começar a dominar o React, espero que este artigo o ajude a escolher o caminho certo para desenvolver um projeto de 1-5 cinco formulários a um grande conjunto de componentes e, ao mesmo tempo, não se confunda.



Se você já é um profissional, talvez se lembre de suas muletas. Ou talvez ofereça melhores soluções para os problemas descritos.



Este artigo se concentrará em minha opinião pessoal sobre como organizar a composição dos componentes.

Vamos começar pequeno



Vamos considerar alguma forma abstrata. Queremos dizer que existem muitos campos no formulário (cerca de 10-15 peças), mas para que os olhos não se percam, tomaremos um formulário com 4 campos como exemplo.



Um objeto multinível do seguinte tipo chega na entrada do componente:



const unit = {
  name: 'unit1',
  color: 'red',
  size: {
    width: 2,
    height: 4,
  },
}


Um desenvolvedor inexperiente (como eu no primeiro mês de trabalho com o react) fará tudo isso em um componente onde os valores de entrada serão armazenados no estado:



const Component = ({ values, onSave, onCancel }) => {
  const [ state, setState ] = useState({});

  useEffect(() => {
    setState(values);
  }, [ values, setState ]);

  return <div className="form-layout">
    <div className="form-field">
      <Input onChange={({ target: { value } }) =>
        setState((state) => ({...state, name: value }))
      }/>
    </div>
    <div className="form-field">
      <Input onChange={({ target: { value } }) =>
        setState((state) => ({...state, color: value }))
      }/>
    </div>
    <div className="size">
      <div className="form-field">
        <Input onChange={({ target: { value } }) =>
          setState((state) => ({...state, size: { width: value } }))
        }/>
      </div>
      <div className="form-field">
        <Input onChange={({ target: { value } }) =>
          setState((state) => ({...state, size: { height: value } }))
        }/>
      </div>
    </div>
    <div className="buttons">
      <Button onClick={() => onSave(state)}>Save</Button>
      <Button onClick={() => onCancel()}>Cancel</Button>
    </div>
  </div>
}


Vendo a rapidez com que o desenvolvedor lidou com isso, o cliente se oferecerá para fazer outro formulário com base neste formulário, mas sem o bloco “tamanho”.



Existem 2 opções (e ambas não estão corretas):



  1. Você pode copiar o primeiro componente e adicionar o que está faltando ou remover o excesso. Isso geralmente é feito quando eles não tomam seu próprio componente como base e têm medo de quebrar algo nele.
  2. Adicione configurações de componentes adicionais aos parâmetros.


Se após a implementação de 3-5 formulários o projeto for concluído, o desenvolvedor está com sorte.



Mas geralmente isso é apenas o começo, e o número de moldes diferentes só está crescendo.



Em seguida, um semelhante é necessário, mas sem o bloco de “cor”.

Em seguida, um semelhante, mas com um novo bloco de "descrição".

Em seguida, faça alguns blocos apenas para leitura.

Em seguida, uma forma semelhante deve ser inserida em outra forma - geralmente tristeza, que às vezes vem disso.



Novos formulários por cópia



Um desenvolvedor que adota uma abordagem de cópia certamente será rápido para implementar novos formulários. Embora haja menos de 10. Mas então o clima cairá gradualmente.



Especialmente quando ocorre um redesenho. Corrija "um pouco" os recuos entre os blocos da forma, altere o componente de seleção de cores. Afinal, nem tudo pode ser previsto de uma vez, e muitas decisões de design terão que ser revisadas após sua implementação.



É importante prestar atenção à menção frequente de "forma semelhante". Afinal, o produto é um e todos os moldes devem ser semelhantes. Como resultado, você tem que lidar com um trabalho muito desinteressante e rotineiro para retrabalhar o mesmo em cada formulário e, a propósito, os testadores também precisarão verificar cada formulário.

Em geral, essa é a ideia. Não copie componentes semelhantes.


Novas formas por generalização



Se o desenvolvedor escolheu o segundo caminho, então é claro que ele está em um cavalo - você pode pensar. Possui apenas alguns componentes com os quais você pode desenhar dezenas de formas. Corrija o recuo ao longo do projeto ou altere o componente “cor” - isso é para corrigir duas linhas no código e o testador terá que verificar novamente em apenas alguns lugares.



Mas, na verdade, esse caminho deu origem a um componente muito difícil de manter.



É difícil usar isso, tk. muitos parâmetros, alguns são chamados quase da mesma forma, para entender pelo que cada parâmetro é responsável, você precisa entrar no interior.



<Component
  isNameVisible={true}
  isNameDisabled={true}
  nameLabel="Model"
  nameType="input"
  isColorVisible={true}
  isColorDisabled={false}
  colorType={'dropdown'}
  isSizeVisible={true}
  isHeightVisible={true}
  isWidthDisabled={false}
/>


Também é difícil de manter. Como regra, existem condições de entrelaçamento complexas e adicionar uma nova condição pode quebrar todo o resto. Ajustar o componente para produzir um formulário pode quebrar o resto.

Em geral, essa é a ideia. Não torne o componente muitas propriedades.
Para resolver os problemas da segunda opção, os desenvolvedores começam o quê? Certo. Como desenvolvedores reais, eles começam a desenvolver algo que torna mais fácil personalizar um componente complexo.

Por exemplo, eles fazem o parâmetro de campos (bem, como colunas na tabela reativa). E os parâmetros dos campos são passados ​​lá: qual campo está visível, qual não é editável, o nome do campo.



A chamada do componente se transforma em:



const FIELDS = {
    name: { visible: true, disabled: true, label: 'Model', type: 'input' },
    color: { visible: true, disabled: false, type: 'dropdown' },
    size: { visible: true },
    height: { visible: true },
    width: { disabled: false },
}
<Component
  values={values}
  fields={FIELDS}
/>


Como resultado, o desenvolvedor está orgulhoso de si mesmo. Ele generalizou as configurações para todos os campos e otimizou o código interno do componente: agora para cada campo é chamada uma função, que converte a configuração em adereços do componente correspondente. Mesmo pelo nome do tipo, um componente diferente é renderizado. Um pouco mais e você terá seu próprio framework.



Legal? Demais.



Espero que não se transforme nisso:



const FIELDS = {
    name: getInputConfig({ visible: true, disabled: true, label: 'Model'}),
    color: getDropDownConfig({ visible: true, disabled: false}),
    size: getBlockConfig({ visible: true }),
    height: getInputNumberConfig({ visible: true }),
    width: getInputNumberConfig({ disabled: false }),
}
<Component
  values={values}
  fields={FIELDS}
/>


Em geral, essa é a ideia. Não reinvente a roda.


Novas formas pela composição de componentes e formas aninhadas



Vamos lembrar o que ainda escrevemos. Já temos uma biblioteca de reações. Não há necessidade de inventar novos designs. A configuração do componente em react é descrita usando a marcação JSX




const Form1 = ({ values }) => {
  return <FormPanel>
    <FormField disabled label=”Model”>
      <Input name="name" />
    </FormField>
    <FormField disabled label=”Color”>
      <DropDown name="color" />
    </FormField>
    <FormPanel>
      <FormField disabled label="Height">
        <Input name="height" />
      </FormField>
      <FormField disabled label="Width">
        <Input name="width" />
     </From Field>
    </FormPanelt>
  </FormPanel>
}


Parece que estamos de volta à primeira opção de cópia. Mas na verdade não. Esta é uma composição que elimina os problemas das duas primeiras abordagens.



Existe um conjunto de tijolos dos quais a forma é montada. Cada tijolo é responsável por algo diferente. Alguns para o layout e aparência, alguns para entrada de dados.



Se precisar alterar os recuos em todo o projeto, basta fazê-lo no componente FormField. Se você precisar alterar a forma como a lista suspensa funciona, isso é feito em um só lugar no componente DropDown.



Se você precisa de uma forma semelhante, mas por exemplo, para que não haja um campo de “cor”, então colocamos os blocos comuns em tijolos separados e montamos outra forma.



Movemos o bloco de tamanho para um componente separado:



const Size = () =>  <FormPanel>
    <FormField disabled label="Height">
      <Input name="height" />
    </FormField>
    <FormField disabled label=”Width”>
      <Input name="width" />
   </From Field>
  </FormPanel>


Fazendo uma forma com uma escolha de cores:



const Form1 = () => <FormPanel>
    <FormField disabled label="Color">
      <DropDown name="color" />
   </FormField>
    <FormField disabled label="Model">
       <Input name="name" />
    </FormField>
    <Size name="size" />
</FormPanel>


Fazemos uma forma semelhante, mas sem escolher uma cor:



const Form2 = () => <FormPanel>
    <FormField disabled label="Model">
       <Input name="name" />
    </FormField>
    <Size name="size" />
</FormPanel>


Mais importante ainda, a pessoa que obtém esse código não precisa lidar com as configurações fictícias do predecessor. Tudo é escrito em JSX, familiar a qualquer desenvolvedor de Reações, com dicas de parâmetros para cada componente.

Em geral, essa é a ideia. Use JSX e composição de componentes.


Algumas palavras sobre o Estado



Agora vamos prestar atenção ao estado. Mais precisamente, sua ausência. Assim que adicionamos um estado, fechamos o fluxo de dados e fica mais difícil reutilizar o componente. Todos os tijolos devem ser sem estado (ou seja, sem estado). E apenas no nível mais alto, a forma montada com tijolos pode ser conectada ao estado. Se o formulário for complexo, faz sentido dividi-lo em vários contêineres e conectar cada parte para redux.



Não seja preguiçoso para fazer um componente separado do formulário sem estado. Então você terá a oportunidade de usá-lo como parte de outro formulário, ou fazer um formulário statefull baseado nele, ou um container para conectar ao redux.



Claro, pode haver estados nos blocos para armazenar o estado interno não associado ao fluxo de dados geral. Por exemplo, no estado interno DropDown (lista suspensa) é conveniente armazenar o atributo, esteja ele expandido ou não.

Em geral, essa é a ideia. Separe os componentes em Stateless e Statefull.


Total



Surpreendentemente, periodicamente encontro todos os erros descritos no artigo e os problemas que surgem deles. Espero que você não os repita e então a manutenção do seu código ficará muito mais fácil.



Vou repetir os pontos principais:



  1. Não copie componentes semelhantes. Use o princípio DRY.
  2. Não faça componentes com muitas propriedades e funcionalidades. Cada componente deve ser responsável por algo diferente (Responsabilidade Única de SÓLIDO)
  3. Separe os componentes em Stateless e Statefull.
  4. Não invente seus próprios projetos. Use JSX e composição de seus componentes.



All Articles