Componentes React: lidar com variações através de props
, ou criar componentes diferentes?
#859
Replies: 11 comments
-
Muito bacana esta dúvida. Isso depende um pouco de como é essa aplicação e quão reaproveitável você gostaria de deixar este componente. Como no seu exemplo está desconsiderando o controle de estado do componente, acredito que sua dúvida seria somente relacionado ao método Enfim... Um approach legal, seria extrair o validate como propriedade, deixando o componente muito mais reaproveitável. Assim vai permitir que você implemente as validações independente do Imagina, toda vez que você pensar em um novo tipo de componente, você vai ter que inserir um Agora tirando a responsabilidade da validação para um nível acima, ou seja, um // Form.js
import { validateName } from './validations';
import { InputField } from './InputField';
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
name: {
value: '',
}
}
}
handleChange(field) {
return (value) => {
this.setState({
[field]: { value },
});
}
}
render() {
return (
<div>
<InputField
value={this.state.name.value}
name="name"
type="text"
onChange={::this.handleChange('name')}
validate={validateName}
/>
</div>
);
}
} Então o seu component // InputField.js
const onHandleChange = function (value, validate, onChange) {
const resultValidate = validate(value);
// implement some code with the result of validate
if (resultValidate) {
onChange(value);
}
}
export const InputField = ({ value, name, type, validate, onChange }) => (
<div>
<input
type={type}
name={name}
value={value}
onChange={(e) => onHandleChange(e.target.value, validate, onChange)}
/>
</div>
); Então você poderia organizar as validações dos campos em um arquivo separado, para depois importá-los no // validations.js
export const validateName = value => {
// implement validateName
return (value && value[0] === '@') ? true : false;
} Vantagens deste approach, seria o reaproveitamento tanto do componente Lembrando que eu implementei este componente Enfim, espero ter conseguido passar a ideia, essa ideia não é a verdade absoluta, mas acredito ser um bom padrão. Vale a pena ser discutido. |
Beta Was this translation helpful? Give feedback.
-
@jmlavoier passou muito bem a ideia! O bom é que sua resposta foi tão completa, que conseguiu me ensinar sobre como pensar melhor a respeito do que considerar como responsabilidade de um componente. Pra fechar, o mais valioso: "Enfim, espero ter conseguido passar a ideia, essa ideia não é a verdade absoluta, mas acredito ser um bom padrão." A ideia é essa: discutir. Cada um mostrar o que pensa e o porquê. |
Beta Was this translation helpful? Give feedback.
-
Antes eu passava uma condição p/ uma mesma função e poder identificar quem está disparando o evento, exatamente com o jmlavoier descreveu. Hoje pensando em perfomance, pra evitar chamar funções no render, eu crio uma função pra setar o estado e uma pra cada trigger atribuida numa variável. changeOrderViewer = order => this.setState({
order
})
sortArrByName = () => this.changeOrderViewer('name')
sortArrByPriceBrl = () => this.changeOrderViewer('price_usd')
...
<span onClick={this.sortArrByName}>Nome</span>
<span onClick={this.sortArrByPriceBrl}>Preço</span> Mas, hoje estava lendo hoje sobre Higher Order Component e lembrei do seu problema, com ele você conseguiria reaproveitar a mesma lógica em componentes diferentes num wrapper. Na documentação está mais detalhada, https://reactjs.org/docs/higher-order-components.html desculpe não ser mais claro, é que estou aprendendo também. |
Beta Was this translation helpful? Give feedback.
-
@SergiojrDEv Legal você falar sobre esses pontos. Evitar inserir as funções direto no render ajuda na implementação de Testes Unitários, apesar de não ser o centro desta discussão é um excelente padrão mesmo. É que no meu caso eu precisava passar alguns parâmetro além do Agora sobre HOC era algo que eu queria falar na minha explicação anterior, mas não falei pois achei que ia ficar muito extenso. Mas já que tocou no assunto, agora vamos trocar essa ideia, até mesmo porque isso realmente tem a ver com a questão de responsabilidade que estamos falando. Isso é assunto meio TL;DR, mas vou tentar ser o mais breve possível, e aplicar no mesmo exemplo que falei anteriormente. No exemplo anterior eu criei o componente input, que recebe como propriedade o // InputField.js
export const InputField = ({ value, name, type, validate, onChange }) => (
<div>
<input
type={type}
name={name}
value={value}
onChange={(e) => onHandleChange(e.target.value, validate, onChange)}
/>
</div>
); Olhando para este pobrezinho componente percebemos que ele não é um componente muito completo, pois nem todos campos são inputs. Precisamos de alguns outros tipos de campos, como o select e textarea por exemplo. Seria legal colocar esta lógica dentro do InputField? Ou será que eu preciso criar outros componentes SelectField e TextAreaField? É justamente essas perguntas que acabam sendo e devem ser feitas. Porém se levarmos em conta a mesma ideia que falei anteriormente (sobre coesão), claro que criar componentes separados seria uma ideia mais interessante. Então criariamos mais dois componentes // SelectField.js
export const InputField = ({ value, name, validate, onChange, options }) => (
<div>
<select
name={name}
value={value}
onChange={(e) => onHandleChange(e.target.value, validate, onChange)}
>
{options.map((option, i)=> <option key={i} value={option} >{option}</option>)}
</div>
); e // TextareaField.js
export const TextareaField = ({ value, name, validate, onChange }) => (
<div>
<textarea
name={name}
value={value}
onChange={(e) => onHandleChange(e.target.value, validate, onChange)}
/>
</div>
); Agora que em a chave da questão, olhando para os três componentes, o que eles teem em comum? Eles têm a implementação de um handleChange que vai fazer a mesma coisa para todos. Isso está redundante, como podemos ser mais DRY? É aí que vem a sacada, podemos criar um componente "container" que vai centralizar essa lógica em um só lugar, ou seja, fazer o trabalho sujo em comum entre os componentes. O nosso High Order Component!!! Acredito que o nome dela pode ser Field, como no redux-form mesmo. Neste caso eu inseri uma lógica a mais, melhorei a implementação da validação para algo mais interessante, mudando a cor da borda para vermelho quando a informação não for válida. HOC vai nada mais nada menos fazer um wrapper de um componente passado por propriedade, e passar algumas propriedades para ele. // Field.js
import React from 'react';
// Esta função é comum entre todos os campos
// Reapare que fiz um ajuste que deixa mais elegante
// a maneira de passar o parametros para o handle
const onHandleChange = (validate, onChange) => event => {
const { value } = event.target;
const isValid = validate(value);
onChange(value, isValid);
}
// Esta função também é comum entre todos os campos
const getStyle = isValid => ({
border: isValid ? '' : '1px solid red',
});
export const Field = ({
component: Component,
validate,
isValid,
onChange,
...props,
}) => (
<div>
<Component
style={getStyle(isValid)}
onChange={onHandleChange(validate, onChange)}
{...props}
/>
</div>
); Com isso, os nossos componentes InputField, SelectField e TextareaField ficam ainda ainda mais responsáveis por sua única função, renderizar os campos. Enquanto a lógicas em comum entre todos eles fica na responsabilidade do Field. // InputField.js
import React from 'react';
export const InputField = ({
value,
name,
type,
style,
onChange
}) => (
<div>
<input
value={value}
style={style}
name={name}
type={type}
onChange={onChange}
/>
</div>
); // SelectField.js
import React from 'react';
export const SelectField = ({
value,
name,
type,
options,
style,
onChange,
}) => (
<div>
<select
name={name}
value={value}
style={style}
onChange={onChange}
>
<option value="">Select</option>
{options.map((option, index) => (
<option
key={index}
value={option} >
{option}
</option>
))}
</select>
</div>
); //TextareaField.js
import React from 'react';
export const TextareaField = ({
value,
name,
style,
onChange,
}) => (
<div>
<textarea
style={style}
value={value}
name={name}
onChange={onChange}
/>
</div>
); Agora como ficaria o Form: // Form.js
import React from 'react';
import {
validateName,
validateSexo,
validateAbout,
} from './formValidations';
import { InputField } from './InputField';
import { SelectField } from './SelectField';
import { TextareaField } from './TextareaField';
import { Field } from './Field';
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
name: { value: '', isValid: true },
sexo: { value: '', isValid: true },
about: { value: '', isValid: true },
}
}
handleChange(field) {
return (value, isValid) => {
this.setState({
[field]: { value, isValid },
});
}
}
render() {
return (
<div>
<Field
component={InputField}
validate={validateName}
value={this.state.name.value}
isValid={this.state.name.isValid}
onChange={::this.handleChange('name')}
/>
<Field
component={SelectField}
validate={validateSexo}
value={this.state.sexo.value}
isValid={this.state.sexo.isValid}
options={['masculino', 'feminino']}
onChange={::this.handleChange('sexo')}
/>
<Field
component={TextareaField}
validate={validateAbout}
value={this.state.about.value}
isValid={this.state.about.isValid}
onChange={::this.handleChange('about')}
/>
</div>
);
}
}
export default Form; Muito legal essa abordagem né!? Espero que eu tenha conseguido explicar de uma maneira simples e sucinta. Não queria que isso virasse um artigo. Mas de uma maneira simplista é basicamente isso, existem alguns padrões que devem ser levados em conta ao implementar uma HOC, legal ler direitinho a documentação que o @SergiojrDEv passou, pois tem mais coisas lá. Se quiserem ver o código funcionando: |
Beta Was this translation helpful? Give feedback.
-
Ótimo exemplo, @jmlavoier, desculpe tocar no assunto e fazer você escrever esse artigo hehe, mas aproveitando, gostaria de sugerir levar pro Medium, não é muito fácil encontrar uma explicação assim em português, pra quem está começando por ajudar muito. |
Beta Was this translation helpful? Give feedback.
-
Verdade @SergiojrDEv. Acredito que não poderíamos omitir estes pontos. Em relação a levar isso pro Medium, ótima ideia, vou fazer isso sim. Valew pela ideia, assim que tiver pronto, compartilho aqui com vocês. |
Beta Was this translation helpful? Give feedback.
-
@StanleySathler e @SergiojrDEv Posso citar o nome, e linkar com o perfil do github de vocês? |
Beta Was this translation helpful? Give feedback.
-
Claro, sem problemas @jmlavoier |
Beta Was this translation helpful? Give feedback.
-
Opa, claro, @jmlavoier. Ainda não li todas as respostas por conta do trampo, mas assim que sair, lerei. 😛 |
Beta Was this translation helpful? Give feedback.
-
E aí galera! Desculpem a demora, mas segue aí o artigo como prometido antes. No final dou uma redirecionada para outro artigo que fala sobre Render Props, que é outro padrão muito interessante para reaproveitamento de código. Galera, podem dar mais de uma palminha (se quiserem é claro). 😄 E vamos discutindo, que acredito ter muito mais coisa para ser falado sobre o assunto. |
Beta Was this translation helpful? Give feedback.
-
Como ficaria o handleSubmit do form, nesse exemplo @jmlavoier ? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Oi pessoal, como vão? 😃
Recentemente, comecei a questionar a maneira correta de lidar com diferenças em componentes e gostaria de discutir com vocês. Os códigos são baseados em React, mas acredito que os conceitos possam ser aplicados a qualquer outra biblioteca baseada em componentes.
Supomos um componente baseado no
input
com duas possibilidades:email
eURL
. O componente é um wrapper proinput
, porque eles requerem validação.Temos então:
Conforme os tipos de
input
crescem e, consequentemente, as variações também, o ideal é controlar o comportamento baseado notype
usandoif
s ou vocês costumam criar diferentes componentes, um para cada tipo, que de certa forma estendem uminput
base?Beta Was this translation helpful? Give feedback.
All reactions