- Desenvolvimento de biblioteca de componentes no React + Storybook
- Desenvolvimento orientado a testes em JS ou como começar a amar a programação
- Migrando um projeto real de Javascript para Typescript - dores e recursos
Agora vamos prosseguir para o artigo.

Quando comecei a aprender React, havia algumas coisas que eu não entendia. E acho que quase todos que estão familiarizados com o React estão fazendo as mesmas perguntas. Tenho certeza disso porque as pessoas estão construindo bibliotecas inteiras para resolver problemas urgentes. Aqui estão duas questões principais com as quais quase todos os desenvolvedores do React parecem se preocupar:
Como um componente acessa as informações (especialmente uma variável de estado) que estão em outro componente? Como um componente chama uma função que está em outro componente?
Desenvolvedores de JavaScript em geral (e desenvolvedores React em particular) têm gravitado cada vez mais para escrever as chamadas funções puras recentemente. Funções que não estão associadas a mudanças de estado. Funções que não precisam de conexões externas de banco de dados. Funções que são independentes do que acontece fora delas.
Claro, funções puras são um objetivo nobre. Mas se você estiver desenvolvendo um aplicativo mais ou menos complexo, não será capaz de limpar todas as funções. Certamente chegará um momento em que você terá que criar pelo menos alguns componentes que estão de alguma forma relacionados a outros componentes. Tentar evitar isso é ridículo. Essas conexões entre componentes são chamadas de dependências .
Em geral, as dependências são ruins e são mais utilizadas somente quando necessário. Mas, novamente, se seu aplicativo cresceu, alguns de seus componentes dependerão necessariamente uns dos outros. Obviamente, os desenvolvedores do React sabem disso, então eles descobriram como fazer com que um componente passe informações críticas, ou funções, para seus componentes filhos.
Abordagem padrão: use adereços para passar valores
Qualquer valor de estado pode ser passado para outro componente por meio de props. Qualquer função pode ser passada para componentes filhos por meio dos mesmos acessórios. É assim que os descendentes sabem quais valores de estado estão armazenados na árvore e podem invocar ações nos componentes pais. Tudo isso, claro, é bom. Mas os desenvolvedores do React estão preocupados com um problema específico.
A maioria dos aplicativos é em camadas. Em aplicativos complexos, as estruturas podem ser aninhadas profundamente. A arquitetura geral pode ser semelhante a esta:
App
→ refere-se a → ContentArea
ContentArea
→ refere-se a → MainContentArea
MainContentArea
→ refere-se a → MyDashboard
MyDashboard
→ refere-se a → MyOpenTickets
MyOpenTickets
→ refere-se a → TicketTable
TicketTable
→ refere-se a uma sequência → TicketRow
Todo
TicketRow
→ refere-se a →TicketDetail
Teoricamente, essa guirlanda pode ser enrolada por um longo tempo. Todos os componentes fazem parte do todo. Mais precisamente, parte de uma hierarquia. Mas aqui surge a pergunta:
O componente
TicketDetail
no exemplo acima pode ler os valores de estado que estão armazenados em ContentArea
? Ou. Um componente pode TicketDetail
chamar funções que estão em ContentArea
?
A resposta para ambas questões é sim. Em teoria, todos os descendentes podem saber sobre todas as variáveis armazenadas nos componentes pai. Eles também podem chamar funções ancestrais - mas com uma grande ressalva. Isso só é possível se esses valores (valores de estado ou função) forem explicitamente passados aos descendentes por meio de props. Caso contrário, os valores de estado ou função do componente não estarão disponíveis para seu componente filho.
Em pequenos aplicativos e utilitários, isso não desempenha um papel especial. Por exemplo, se um componente
TicketDetail
precisa acessar as variáveis de estado que estão armazenadas TicketRow
, basta fazer com que o componente TicketRow
→ passe esses valores para o seu descendente → TicketDetail
por meio de um ou mais adereços. O mesmo acontece quando um componente TicketDetail
precisa chamar uma função que está em TicketRow
. Componente TicketRow
→ irá passar esta função para seu descendente → TicketDetail
via prop. A dor de cabeça começa quando um componente muito abaixo na árvore precisa acessar o estado ou a função do componente no topo da hierarquia.
Para resolver esse problema no React, as variáveis e funções são tradicionalmente transmitidas para todos os níveis. Mas isso confunde o código, consome recursos e requer um planejamento sério. Teríamos que passar valores para vários níveis como este:
ContentArea
→ MainContentArea
→ MyDashboard
→ MyOpenTickets
→ TicketTable
→ TicketRow
→ TicketDetail
Ou seja, para passar uma variável de estado de
ContentArea
para TicketDetail
, precisamos fazer muito trabalho. Desenvolvedores experientes entendem que existe uma longa e feia cadeia de passagem de valores e funções na forma de adereços por meio de níveis intermediários de componentes. A solução é tão complicada que até desisti de aprender React algumas vezes por causa disso.
O monstro chamado Redux
Não sou o único que pensa que passar todos os valores de estado e todas as funções comuns aos componentes por meio de acessórios é muito impraticável. É improvável que você encontre qualquer aplicativo React complexo que não venha com uma ferramenta de gerenciamento de estado. Não existem poucas dessas ferramentas. Pessoalmente, adoro o MobX. Infelizmente, Redux é considerado o "padrão da indústria".
Redux é uma ideia dos criadores do núcleo React. Ou seja, eles primeiro criaram uma biblioteca React maravilhosa. Mas eles imediatamente perceberam que com ela era quase impossível administrar o estado. Se eles não tivessem encontrado uma maneira de resolver os problemas inerentes a esta biblioteca (de outra forma excelente), muitos de nós nunca teríamos ouvido falar do React.
Então eles vieram com Redux.
Se React for Mona Lisa, então Redux é um bigode anexado a ele. Se você estiver usando Redux, terá que escrever uma tonelada de código clichê em quase todos os arquivos de projeto. A solução de problemas e a leitura de códigos se tornam um inferno. A lógica de negócios é levada para o quintal. O código contém confusão e vacilação.
Mas se os desenvolvedores tiverem escolha: React + Redux ou React sem nenhuma ferramenta de gerenciamento de estado de terceiros , eles quase sempre escolhem React + Redux. Como a biblioteca Redux foi desenvolvida pelos principais autores do React, ela é considerada uma solução aprovada por padrão. E a maioria dos desenvolvedores prefere usar soluções que foram tacitamente aprovadas dessa forma.
Claro que Redux irá criar toda uma teia de dependênciasem seu aplicativo React. Mas, para ser justo, qualquer ferramenta genérica de gerenciamento de estado fará o mesmo. A ferramenta de gerenciamento de estado é um repositório compartilhado de variáveis e funções. Essas funções e variáveis podem ser usadas por qualquer componente que tenha acesso ao armazenamento compartilhado. Isso tem uma desvantagem óbvia: todos os componentes se tornam dependentes do armazenamento compartilhado.
A maioria dos desenvolvedores do React que conheço que tentaram resistir ao uso do Redux desistiram. (Porque ... resistência é inútil.) Conheço muitas pessoas que odeiam Redux imediatamente. Mas quando eles se depararam com uma escolha - Redux ou "vamos encontrar outro desenvolvedor React" - eles se atiraramconcordaram em abraçar Redux como parte integrante de suas vidas. É como impostos. Como um exame retal. Como ir ao dentista.
Reagindo Valores Compartilhados em Reação
Sou muito teimoso para desistir tão facilmente. Depois de olhar para o Redux, percebi que precisava buscar outras soluções. I pode usar Redux. E trabalhei em equipes que usaram essa biblioteca. Em geral, eu entendo o que ela faz. Mas isso não significa que eu goste do Redux.
Como eu disse antes, se você não pode viver sem uma ferramenta de gerenciamento de estado separada, então o MobX é cerca de ... um milhão de vezes melhor do que o Redux! Mas estou atormentado por uma questão mais séria. Isso toca a mente coletiva dos desenvolvedores do React:
por que sempre pegamos uma ferramenta de gerenciamento de estado primeiro?
Quando comecei a desenvolver com React, passei muitas noites procurando soluções alternativas. E descobri uma maneira que muitos desenvolvedores do React negligenciam, mas nenhum deles sabe dizer por quê . Vou explicar.
Imagine que no aplicativo hipotético que escrevi acima, criamos um arquivo como este:
// components.js
let components = {};
export default components;
E isso é tudo. Apenas duas linhas curtas de código. Criamos um objeto vazio - o bom e velho objeto JS . Nós o exportamos por padrão com
export default
.
Agora vamos ver a aparência do código dentro do componente
<
ContentArea>
:
// content.area.js
import components from './components';
import MainContentArea from './main.content.area';
import React from 'react';
export default class ContentArea extends React.Component {
constructor(props) {
super(props);
components.ContentArea = this;
}
consoleLog(value) {
console.log(value);
}
render() {
return <MainContentArea/>;
}
}
Na maior parte, parece um componente React baseado em classe perfeitamente normal. Temos uma função simples
render()
que acessa o próximo componente na árvore. Temos uma pequena função console.log()
que imprime o resultado da execução do código no console e um construtor. Mas ... existem algumas nuances no construtor .
No início, importamos um objeto simples
components
. Então, no construtor, adicionamos uma nova propriedade ao objeto components
com o nome do componente React atual ( this
) .Nesta propriedade nos referimos ao componente this
. Agora, toda vez que acessarmos o objeto components, teremos acesso direto ao componente <
ContentArea>
.
Vamos ver o que acontece na parte inferior da hierarquia. O componente
<
TicketDetail>
pode ser assim:
// ticket.detail.js
import components from './components';
import React from 'react';
export default class TicketDetail extends React.Component {
render() {
components.ContentArea.consoleLog('it works');
return <div>Here are the ticket details.</div>;
}
}
Aqui está o que acontece. Cada vez que o componente
TicketDetail
é renderizado consoleLog()
, a função que está armazenada no componente será chamada ContentArea
.
Observe que a função
consoleLog()
não é passada por toda a hierarquia por meio de adereços. Na verdade, a função consoleLog()
não é passada a lugar nenhum - nem a lugar nenhum - a nenhum componente.
E ainda
TicketDetail
pode chamar a função consoleLog()
que está armazenada em ContentArea
, porque fizemos duas coisas:
ContentArea
Quando carregado, o componente adicionou um link para si mesmo ao objeto compartilhado do componente.TicketDetail
Ao ser carregado, o componente importava um objeto compartilhadocomponents
, ou seja, tinha acesso direto ao componenteContentArea
, embora as propriedadesContentArea
não fossem passadas ao componenteTicketDetail
por meio de props.
Essa abordagem não funciona apenas com funções / callbacks. Ele pode ser usado para consultar diretamente os valores das variáveis de estado. Vamos imaginar o que se
<
ContentArea>
parece com isto:
// content.area.js
import components from './components';
import MainContentArea from './main.content.area';
import React from 'react';
export default class ContentArea extends React.Component {
constructor(props) {
super(props);
this.state = { reduxSucks:true };
components.ContentArea = this;
}
render() {
return <MainContentArea/>;
}
}
Então podemos escrever
<
TicketDetail>
assim:
// ticket.detail.js
import components from './components';
import React from 'react';
export default class TicketDetail extends React.Component {
render() {
if (components.ContentArea.state.reduxSucks === true) {
console.log('Yep, Redux is da sux');
}
return <div>Here are the ticket details.</div>;
}
}
Agora, sempre que o componente for renderizado
<
TicketDetail
, ele procurará o valor da variável state.reduxSucks
em <
ContentArea>
. Se a variável retornar um valor true
, a função console.log()
imprimirá uma mensagem no console. Isso acontecerá mesmo se o valor da variável ContentArea.state.reduxSucks
nunca tiver sido passado pela árvore - para qualquer um dos componentes - por meio de props. Portanto, com um objeto JS subjacente simples que vive fora do ciclo de vida padrão do React, podemos fazer com que qualquer descendente possa ler variáveis de estado diretamente de qualquer pai carregado no objeto de componentes. Podemos até chamar funções do componente pai em seu descendente.
A capacidade de chamar uma função diretamente em componentes filhos significa que podemos alterar o estado dos componentes pais diretamente de seus filhos. Por exemplo, assim.
Primeiro, no componente,
<
ContentArea>
criaremos uma função simples que altera o valor de uma variável reduxSucks
.
// content.area.js
import components from './components';
import MainContentArea from './main.content.area';
import React from 'react';
export default class ContentArea extends React.Component {
constructor(props) {
super(props);
this.state = { reduxSucks:true };
components.ContentArea = this;
}
toggleReduxSucks() {
this.setState((previousState, props) => {
return { reduxSucks: !previousState.reduxSucks };
});
}
render() {
return <MainContentArea/>;
}
}
Então, no componente,
<
TicketDetail>
chamaremos este método por meio do objeto components
:
// ticket.detail.js
import components from './components';
import React from 'react';
export default class TicketDetail extends React.Component {
render() {
if (components.ContentArea.state.reduxSucks === true) {
console.log('Yep, Redux is da sux');
}
return (
<>
<div>Here are the ticket details.</div>
<button onClick={() => components.ContentArea.toggleReduxSucks()}>Toggle reduxSucks</button>
</>
);
}
}
Agora, após cada renderização de um componente, o
<
TicketDetail>
usuário poderá apertar um botão que mudará (alternará) o valor da variável ContentArea.state.reduxSucks
em tempo real, mesmo que a função ContentArea.toggleReduxSucks()
nunca tenha sido passada para baixo na árvore através de adereços.
Com essa abordagem, o componente pai pode chamar a função diretamente de seu filho. Veja como fazer. O componente atualizado
<
ContentArea>
terá a seguinte aparência:
// content.area.js
import components from './components';
import MainContentArea from './main.content.area';
import React from 'react';
export default class ContentArea extends React.Component {
constructor(props) {
super(props);
this.state = { reduxSucks:true };
components.ContentArea = this;
}
toggleReduxSucks() {
this.setState((previousState, props) => {
return { reduxSucks: !previousState.reduxSucks };
});
components.TicketTable.incrementReduxSucksHasBeenToggledXTimes();
}
render() {
return <MainContentArea/>;
}
}
Agora vamos adicionar lógica ao componente
<
TicketTable>
. Como isso:
// ticket.table.js
import components from './components';
import React from 'react';
import TicketRow from './ticket.row';
export default class TicketTable extends React.Component {
constructor(props) {
super(props);
this.state = { reduxSucksHasBeenToggledXTimes: 0 };
components.TicketTable = this;
}
incrementReduxSucksHasBeenToggledXTimes() {
this.setState((previousState, props) => {
return { reduxSucksHasBeenToggledXTimes: previousState.reduxSucksHasBeenToggledXTimes + 1};
});
}
render() {
const {reduxSucksHasBeenToggledXTimes} = this.state;
return (
<>
<div>The `reduxSucks` value has been toggled {reduxSucksHasBeenToggledXTimes} times</div>
<TicketRow data={dataForTicket1}/>
<TicketRow data={dataForTicket2}/>
<TicketRow data={dataForTicket3}/>
</>
);
}
}
Como resultado, o componente
<
TicketDetail>
não mudou. Ainda se parece com isto:
// ticket.detail.js
import components from './components';
import React from 'react';
export default class TicketDetail extends React.Component {
render() {
if (components.ContentArea.state.reduxSucks === true) {
console.log('Yep, Redux is da sux');
}
return (
<>
<div>Here are the ticket details.</div>
<button onClick={() => components.ContentArea.toggleReduxSucks()}>Toggle reduxSucks</button>
</>
);
}
}
Você notou a estranheza associada a essas três classes? Na hierarquia de nosso aplicativo
ContentArea
, este é o componente pai de TicketTable
, que é o componente pai de TicketDetail
. Isso significa que quando montamos um componente ContentArea
, ele ainda não "sabe" sobre sua existência, TicketTable
e a função toggleReduxSucks()
escrita em ContentArea
chama implicitamente a função filha:
incrementReduxSucksHasBeenToggledXTimes()
Acontece que o código não funcionará , certo?
Mas não.
Veja. Criamos vários níveis no aplicativo e só existe uma maneira de chamar a função
toggleReduxSucks()
. Como isso.
- Montamos e renderizamos
ContentArea
. - Durante esse processo, uma referência aos componentes é carregada no objeto de componentes
ContentArea
. - O resultado é montado e renderizado
TicketTable
. - Durante esse processo, uma referência aos componentes é carregada no objeto de componentes
TicketTable
. - O resultado é montado e renderizado
TicketDetail
. - « reduxSucks» (Toggle reduxSucks).
- « reduxSucks».
-
toggleReduxSucks()
,ContentArea
. -
incrementReduxSucksHasBeenToggledXTimes()
TicketTable
. - , , « reduxSucks»,
TicketTable
components.toggleReduxSucks()
ContentArea
incrementReduxSucksHasBeenToggledXTimes()
,TicketTable
, components.
Acontece que a hierarquia de nosso aplicativo nos permite adicionar um
ContentArea
algoritmo ao componente que irá chamar uma função do componente filho, apesar do componente ContentArea
não saber da existência do componente quando foi montadoTicketTable
.
Ferramentas de gerenciamento de patrimônio - despejo
Como expliquei, estou profundamente convencido de que Redux não é páreo para MobX. E quando tenho o privilégio de trabalhar em um projeto do zero (infelizmente não é frequente), sempre faço campanha para a MobX. Não para Redux. Mas quando eu desenvolvo meus próprios aplicativos , raramente uso ferramentas de gerenciamento de estado de terceiros - quase nunca . Em vez disso, apenas armazeno objetos / componentes em cache sempre que possível. E se essa abordagem não funcionar, muitas vezes eu volto para a solução padrão no React, ou seja, eu apenas passo funções / variáveis de estado por meio de adereços.
"Problemas" conhecidos com esta abordagem
Estou bem ciente de que minha ideia de armazenar em cache o objeto subjacente
components
nem sempre é adequada para resolver problemas de estado / função compartilhados. Às vezes, essa abordagem pode ... pregar uma piada cruel . Ou pode não funcionar de todo . Aqui está algo para se manter em mente.
- Funciona melhor com solteiros .
Por exemplo, em nossa hierarquia, o componente <TicketTable> contém componentes <TicketRow> com um relacionamento de zero para muitos. Se você deseja armazenar em cache a referência para cada componente potencial dentro dos componentes <TicketRow> (e seus componentes <TicketDetail> filho) no cache de componentes, você deve armazená-los em um array, e isso pode ser complicado. Sempre evitei isso. - components , / , components. .
, . , , . / , , components. - ,
components
, (setState()
),setState()
, .
Agora que expliquei minha abordagem e algumas de suas limitações, devo avisá-lo. Desde que descobri essa abordagem, tenho compartilhado com pessoas que se consideram desenvolvedores profissionais do React. Cada vez, eles respondem a mesma coisa:
Hmm ... Não faça isso. Eles franzem a testa e agem como se eu tivesse bagunçado o ar. Algo em minha abordagem parece a eles ... errado . Ao mesmo tempo, ninguém ainda me explicou, com base em sua rica experiência prática, o que exatamente está errado. É que todo mundo considera minha abordagem ... blasfêmia .
Portanto, mesmo se você gostar dessa abordagem ou se achar conveniente em algumas situações, eu não recomendofale sobre isso em uma entrevista se quiser conseguir um emprego como desenvolvedor React. Acho que mesmo apenas conversando com outros desenvolvedores do React, você precisa pensar um milhão de vezes antes de falar sobre esse método, ou talvez seja melhor não dizer nada.
Descobri que os desenvolvedores JS - e os desenvolvedores React em particular - podem ser extremamente categóricos . Às vezes, eles explicam porque a abordagem A está “errada” e a abordagem B está “certa”. Mas, na maioria dos casos, eles apenas olham para um trecho de código e o declaram “ruim”, mesmo que eles próprios não consigam explicar por quê.
Então, por que essa abordagem é tão irritante para os desenvolvedores do React?
Como eu disse, nenhum de meus colegas poderia responder razoavelmente por que meu método é ruim. E se alguém está disposto a me honrar com uma resposta, geralmente é uma das seguintes desculpas (há poucas delas).
- , .
.... , , Redux ( MobX, ) / React-. , . — . , /, . : ,components
. , /components
, / , components. /,components
, components . , , . , , Redux, MobX, - . - React « ». … .
… . ? , . — - « » « », , , . React, . , . . « », . , React 100 %, ( ) , .
, ?
Escrevi este post porque tenho usado essa abordagem há anos (em projetos pessoais). E funciona muito bem . Mas sempre que saio da minha bolha pessoal e tento ter uma conversa inteligente sobre essa abordagem com outros desenvolvedores React terceirizados , só me deparo com afirmações categóricas e julgamentos tolos sobre "padrões da indústria".
Esta abordagem é realmente ruim ? Bem, realmente. Eu quero saber. Se este for realmente um "antipadrão", ficarei imensamente grato àqueles que justificarem sua incorrecção. A resposta "Não estou acostumado com isso" não combina comigo. Não, eu não sou obcecado por esse método. Não estou sugerindo que isso seja uma panacéia para os desenvolvedores do React. E admito que não funciona para todas as situações. Mas talvezAlguém pode me explicar o que há de errado com isso?
Eu realmente quero saber sua opinião sobre este assunto - mesmo se você me explodir em pedacinhos.