Antipatterns em React ou Bad Tips for Beginners

Oi Habr.

Exatamente um ano se passou desde que comecei a estudar o React. Durante esse período, consegui liberar vários aplicativos móveis pequenos escritos no React Native e participar do desenvolvimento de um aplicativo Web usando o ReactJS. Resumindo e olhando para trás todos os ancinhos que consegui pisar, tive a idéia de expressar minha experiência na forma de um artigo. Farei uma reserva de que, antes de iniciar o estudo da reação, eu tinha 3 anos de experiência em desenvolvimento em c ++, python, bem como a opinião de que não há nada complicado no desenvolvimento de front-end e não será difícil entender tudo. Portanto, nos primeiros meses, deixei de ler a literatura educacional e basicamente apenas google exemplos de códigos prontos. Assim, um desenvolvedor exemplar que estuda a documentação antes de tudo, provavelmente, não encontrará nada de novo aqui, mas ainda acho que algumas pessoas quando estudam novas tecnologias preferem o caminho da prática à teoria. Portanto, se este artigo salvar alguém de um rake, tentei não em vão.

Dica 1. Trabalhando com Formulários


A situação clássica: existe um formulário com vários campos nos quais o usuário digita os dados, clica no botão e os dados inseridos são enviados para uma API externa / salva no estado / exibida na tela - sublinhe o necessário.

Opção 1. Como não fazer


No React, você pode criar um link para um nó DOM ou componente do React.

this.myRef = React.createRef(); 

Usando o atributo ref, o link criado pode ser anexado ao componente / nó necessário.

 <input id="data" type="text" ref={this.myRef} /> 

Assim, o problema acima pode ser resolvido criando uma ref para cada campo do formulário e, no corpo da função chamada quando o botão é clicado, obtenha os dados do formulário entrando em contato com os links necessários.

 class BadForm extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); this.onClickHandler = this.onClickHandler.bind(this); } onClickHandler() { const data = this.myRef.current.value; alert(data); } render() { return ( <> <form> <label htmlFor="data">Bad form:</label> <input id="data" type="text" ref={this.myRef} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } } 

Como o macaco interior pode tentar justificar esta decisão:

  1. A principal coisa que funciona é que você ainda tem 100500 tarefas e os programas de TV não são assistidos; os ingressos não estão fechados. Deixe assim, depois mude
  2. Veja como é necessário pouco código para processar o formulário. Referência declarada e acesse os dados onde quiser.
  3. Se você armazenar o valor no estado, toda vez que alterar os dados de entrada, o aplicativo inteiro será renderizado novamente e você precisará apenas dos dados finais. Portanto, esse método também é bom para otimização, apenas deixe assim.

Por que o macaco está errado:

O exemplo acima é o antipadrão clássico no React, que viola o conceito de um fluxo de dados unidirecional. Nesse caso, seu aplicativo não poderá responder às alterações de dados durante a entrada, pois elas não são armazenadas no estado.

Opção 2. Solução clássica


Para cada campo de formulário, uma variável é criada no estado em que o resultado da entrada será armazenado. O atributo value é atribuído a esta variável. O atributo onhange recebe uma função na qual o valor da variável in state é alterado através de setState (). Assim, todos os dados são obtidos do estado e, quando os dados são alterados, o estado é alterado e o aplicativo é renderizado novamente.

 class GoodForm extends React.Component { constructor(props) { super(props); this.state = { data: '' }; this.onChangeData = this.onChangeData.bind(this); this.onClickHandler = this.onClickHandler.bind(this); } onChangeData(event) { this.setState({ data: event.target.value }); } onClickHandler(event) { const { data } = this.state; alert(data); } render() { const { data } = this.state; return ( <> <form> <label htmlFor="data">Good form:</label> <input id="data" type="text" value={data} onChange={this.onChangeData} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } } 

Opção 3. Avançado. Quando os formulários se tornam muitos


A segunda opção tem várias desvantagens: uma grande quantidade de código padrão; para cada campo, é necessário declarar o método onhange e adicionar uma variável ao estado. Quando se trata de validar dados inseridos e exibir mensagens de erro, a quantidade de código aumenta ainda mais. Para facilitar o trabalho com formulários, existe uma excelente biblioteca Formik que cuida de questões relacionadas à manutenção de formulários e também facilita a adição de um esquema de validação.

 import React from 'react'; import { Formik } from 'formik'; import * as Yup from 'yup'; const SigninSchema = Yup.object().shape({ data: Yup.string() .min(2, 'Too Short!') .max(50, 'Too Long!') .required('Data required'), }); export default () => ( <div> <Formik initialValues={{ data: '' }} validationSchema={SigninSchema} onSubmit={(values) => { alert(values.data); }} render={(props) => ( <form onSubmit={props.handleSubmit}> <label>Formik form:</label> <input type="text" onChange={props.handleChange} onBlur={props.handleBlur} value={props.values.data} name="data" /> {props.errors.data && props.touched.data ? ( <div>{props.errors.data}</div> ) : null} <button type="submit">Ok</button> </form> )} /> </div> ); 

Dica 2. Evite Mutação


Considere um aplicativo simples da lista de tarefas. No construtor, definimos em estado a variável na qual a lista de tarefas será armazenada. No método render (), exiba o formulário através do qual adicionaremos casos à lista. Agora considere como podemos mudar de estado.

Opção incorreta que leva à mutação do array:

 this.state.data.push(item); 

Nesse caso, a matriz realmente mudou, mas o React não sabe nada sobre ela, o que significa que o método render () não será chamado e nossas alterações não serão exibidas. O fato é que, em JavaScript, ao criar uma nova matriz ou objeto, o link é salvo na variável, e não no próprio objeto. Assim, adicionando um novo elemento à matriz de dados, alteramos a matriz em si, mas não o link para ela, o que significa que o valor dos dados armazenados no estado não será alterado.

Mutações no JavaScript podem ser encontradas a cada passo. Para evitar mutações nos dados, use o operador de propagação para matrizes, métodos filter () e map () e, para objetos, use o operador de propagação ou o método assign ().

 const newData = [...data, item]; const copy = Object.assign({}, obj); 

Voltando ao nosso aplicativo, vale dizer que a opção correta para alterar o estado é usar o método setState (). Não tente mudar o estado diretamente em nenhum outro lugar que não seja o construtor, pois isso contradiz a ideologia do React.

Não faça isso!

 this.state.data = [...data, item]; 

Evite também a mutação de estado. Mesmo se você usar setState (), as mutações podem levar a erros ao tentar otimizar. Por exemplo, se você passar um objeto alterado por meio de adereços para um PureComponent filho, esse componente não poderá entender que os adereços recebidos foram alterados e não serão renderizados novamente.

Não faça isso!

 this.state.data.push(item); this.setState({ data: this.state.data }); 

A opção correta:

 const { data } = this.state; const newData = [...data, item]; this.setState({ data: newData }); 

Mas mesmo a opção acima pode levar a erros sutis. O fato é que ninguém garante que, durante o tempo decorrido entre o recebimento da variável de dados de estado e a gravação de seu novo valor em estado, o próprio estado não será alterado. Assim, você corre o risco de perder algumas das alterações feitas. Portanto, no caso em que você precise atualizar o valor de uma variável no estado usando seu valor anterior, faça o seguinte:

A opção correta, se a seguinte condição depender da corrente:

 this.setState((state) => { return {data: [...state.data, item]}; }); 

Dica 3. Emulando um aplicativo de várias páginas


Seu aplicativo está em desenvolvimento e, em algum momento, você percebe que precisa de várias páginas. Mas o que fazer, porque o React é um aplicativo de página única? Nesse ponto, a seguinte ideia maluca pode vir à sua mente. Você decide que manterá o identificador da página atual no estado global do seu aplicativo, por exemplo, usando o repositório redux. Para exibir a página desejada, você usará a renderização condicional e alternará entre as páginas, chamando a ação com a carga útil desejada, alterando os valores no redux da loja.

App.js

 import React from 'react'; import { connect } from 'react-redux'; import './App.css'; import Page1 from './Page1'; import Page2 from './Page2'; const mapStateToProps = (state) => ({ page: state.page }); function AppCon(props) { if (props.page === 'Page1') { return ( <div className="App"> <Page1 /> </div> ); } return ( <div className="App"> <Page2 /> </div> ); } const App = connect(mapStateToProps)(AppCon); export default App; 

Page1.js

 import React from 'react'; import { connect } from 'react-redux'; import { setPage } from './redux/actions'; function mapDispatchToProps(dispatch) { return { setPageHandle: (page) => dispatch(setPage(page)), }; } function Page1Con(props) { return ( <> <h3> Page 1 </h3> <input type="button" value="Go to page2" onClick={() => props.setPageHandle('Page2')} /> </> ); } const Page1 = connect(null, mapDispatchToProps)(Page1Con); export default Page1; 

Por que isso é ruim?

  1. Esta solução é um exemplo de uma bicicleta primitiva. Se você sabe como fazer uma bicicleta com competência e entende o que está procurando, não cabe a mim aconselhá-lo. Caso contrário, seu código será implícito, confuso e excessivamente complexo.
  2. Você não poderá usar o botão Voltar no navegador, pois o histórico de visitas não será salvo.

Como resolver isso?

Basta usar o react-router . Este é um ótimo pacote que pode facilmente transformar seu aplicativo em um de várias páginas.

Dica 4. Onde colocar solicitações de API


Em algum momento, você precisava adicionar uma solicitação a uma API externa no seu aplicativo. E aqui você está se perguntando: onde em seu aplicativo você precisa executar a solicitação?
No momento, ao montar um componente React, seu ciclo de vida é o seguinte:

  • construtor ()
  • static getDerivedStateFromProps ()
  • render ()
  • componentDidMount ()

Vamos analisar todas as opções em ordem.

No método constructor (), a documentação não recomenda fazer nada além de:

  • Inicializando o estado interno através da atribuição do objeto this.state.
  • Ligações de manipuladores de eventos a uma instância.

As chamadas para API não estão incluídas nesta lista, então vamos seguir em frente.

O método getDerivedStateFromProps () de acordo com a documentação existe para situações raras em que o estado depende de alterações nos acessórios. Novamente, não é o nosso caso.

O erro mais comum é a localização do código que executa solicitações de API no método render (). Isso leva ao fato de que, assim que sua solicitação for executada com sucesso, você provavelmente salvará o resultado no estado do componente e isso levará a uma nova chamada ao método render (), na qual sua solicitação à API será executada novamente. Assim, seu componente terminará em renderização sem fim, e isso claramente não é o que você precisa.

Portanto, o método componentDidMount () é o local ideal para acessar a API externa.

Conclusão


Exemplos de código podem ser encontrados no github .

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


All Articles