Guia completo de UseEffect

Você escreveu vários componentes usando ganchos . Talvez - eles até criaram um pequeno aplicativo. Em geral, você está bastante satisfeito com o resultado. Você está acostumado à API e, no processo, encontrou alguns truques úteis inesperados. Você até criou alguns de seus próprios ganchos e reduziu seu código para 300 linhas, colocando neles o que era representado anteriormente pela repetição de fragmentos de programa. O que você fez, você mostrou colegas. "Muito bem", disseram eles sobre o seu projeto.


Mas, às vezes, quando você usa o useEffect , os componentes dos mecanismos do software não se encaixam muito bem. Parece que você está perdendo alguma coisa. Tudo isso é semelhante a trabalhar com eventos de ciclo de vida de componentes com base em classe ... mas é realmente assim?
Tentando entender o que exatamente não combina com você, você percebe que está fazendo as seguintes perguntas:

  • Como jogar componentDidMount usando useEffect ?
  • Como carregar dados dentro do useEffect ? O que é [] ?
  • As funções precisam ser especificadas como dependências de efeito?
  • Por que um programa às vezes acaba em um loop infinito de recarga de dados?
  • Por que o estado antigo às vezes é visível dentro dos efeitos ou são encontradas propriedades antigas?

Quando comecei a usar ganchos, essas perguntas também me atormentaram. Mesmo quando eu estava preparando a documentação, não podia dizer que domino perfeitamente algumas das sutilezas. Desde então, tive vários momentos em que, de repente, percebendo algo importante, eu realmente queria exclamar: "Eureka!" Sobre o que percebi nesses momentos, quero lhe contar. O que você aprendeu sobre o useEffect agora permitirá que você veja claramente as respostas óbvias às perguntas acima.

Mas, para ver as respostas a essas perguntas, precisamos primeiro dar um passo atrás. O objetivo deste artigo não é fornecer aos leitores instruções passo a passo para trabalhar com o useEffect . Ele visa ajudá-lo, por assim useEffect " useEffect " o useEffect . E, francamente, não há muito o que aprender. De fato, na maior parte do tempo, gastamos esquecendo o que sabíamos antes.

Tudo na minha cabeça se uniu somente depois que parei de olhar para o gancho useEffect através do prisma dos métodos familiares do ciclo de vida dos componentes baseados em componentes.

"Você deve esquecer o que foi ensinado"


habr.com/ru/company/ruvds/blog/445276/Yoda


Supõe-se que o leitor deste material esteja familiarizado com a API useEffect . Este é um artigo bastante longo, que pode ser comparado com um pequeno livro. O fato é que prefiro expressar meus pensamentos dessa maneira. Abaixo, muito brevemente, são dadas respostas para as perguntas que foram discutidas acima. Talvez sejam úteis para aqueles que não têm tempo ou desejam ler todo o material.

Se o formato em que vamos considerar o useEffect , com todas as suas explicações e exemplos, não for muito adequado para você, aguarde um pouco - até o momento em que essas explicações aparecerem em inúmeros outros manuais. Aqui está a mesma história que a própria biblioteca do React, que em 2013 era algo completamente novo. Leva algum tempo para que a comunidade de desenvolvimento reconheça o novo modelo mental e apareça o material educacional baseado nesse modelo.

Respostas às perguntas


Aqui estão respostas curtas para as perguntas feitas no início deste material, destinadas a quem não deseja ler todo o texto. Se, ao ler essas respostas, você sentir que realmente não entende o significado do que lê, examine o material. Você encontrará explicações detalhadas no texto. Se você está lendo tudo, pode pular esta seção.

PlayComo reproduzir componentDidMount usando useEffect?


Embora você possa usar a construção useEffect(fn, []) para reproduzir a funcionalidade componentDidMount , ela não é o equivalente exato de componentDidMount . Ou seja, ao contrário de componentDidMount , captura propriedades e estado. Portanto, mesmo dentro do retorno de chamada, você verá as propriedades e o estado iniciais. Se você quiser ver a versão mais recente de algo, escreva-a no link ref . Mas geralmente há uma maneira mais simples de estruturar o código, portanto, isso é opcional. Lembre-se de que o modelo de efeitos mentais é diferente daquele aplicável ao componentDidMount e a outros métodos de ciclo de vida do componente. Portanto, tentar encontrar os equivalentes exatos pode fazer mais mal do que bem. Para trabalhar produtivamente, você precisa, por assim dizer, "pensar em efeitos". A base de seu modelo mental está mais próxima da implementação da sincronização do que de responder a eventos no ciclo de vida dos componentes.

LoadComo carregar corretamente os dados no useEffect? O que é []?


Aqui está um bom guia para carregar dados usando useEffect . Tente ler na íntegra! Não é tão grande quanto isso. Os colchetes, [] , representando uma matriz vazia, significam que o efeito não usa os valores que participam do fluxo de dados React e, por esse motivo, seu uso único pode ser considerado seguro. Além disso, o uso de uma matriz vazia de dependências é uma fonte comum de erros no caso de um determinado valor ser realmente usado no efeito. Você precisará dominar várias estratégias (principalmente apresentadas na forma de useReducer e useCallback ) que podem ajudar a eliminar a necessidade de uma dependência, em vez de descartá-la de maneira irracional.

Functions As funções precisam ser especificadas como dependências de efeito?


Recomenda-se que as funções que não precisam de propriedades ou de um estado sejam tomadas fora dos componentes e que as funções usadas apenas pelos efeitos sejam colocadas dentro dos efeitos. Se depois disso, seu efeito ainda usar funções que estão no escopo da renderização (incluindo funções das propriedades), envolva-as em useCallback onde elas são declaradas e tente usá-las novamente. Por que isso é importante? As funções podem "ver" os valores das propriedades e do estado, para que participem do fluxo de dados. Aqui estão informações mais detalhadas sobre isso em nossas Perguntas frequentes.

▍ Por que um programa às vezes acaba em um loop infinito de recarga de dados?


Isso pode acontecer quando o carregamento de dados é realizado em um efeito que não possui um segundo argumento representando as dependências. Sem ele, os efeitos são executados após cada operação de renderização - o que significa que definir o estado fará com que esses efeitos sejam recuperados. Um loop infinito também pode ocorrer se um valor que sempre muda for indicado na matriz de dependências. Descubra que tipo de valor é possível removendo dependências uma por vez. No entanto, remover dependências (ou imprudentemente usar [] ) geralmente é a abordagem errada para resolver um problema. Em vez disso, você deve encontrar a fonte do problema e realmente resolvê-lo. Por exemplo, funções podem causar um problema semelhante. Você pode ajudar a resolvê-lo colocando-os em efeito, movendo-os para fora dos componentes ou useCallback em useCallback . Para evitar a criação de vários objetos, você pode usar useMemo .

▍ Por que às vezes o estado antigo é visível dentro dos efeitos ou são encontradas propriedades antigas?


Os efeitos sempre “veem” as propriedades e o estado da renderização em que são declarados. Isso ajuda a evitar erros , mas em alguns casos, pode interferir na operação normal do componente. Nesses casos, você pode usar links de ref mutável explicitamente para trabalhar com esses valores (você pode ler sobre isso no final do artigo mencionado acima). Se você acha que vê propriedades ou estado a partir da renderização antiga, mas não espera isso, pode ter perdido algumas dependências. Para aprender a vê-los, use esta regra do linter. Em alguns dias, isso se tornará algo como sua segunda natureza. Além disso, dê uma olhada nesta resposta em nossas Perguntas frequentes.

Espero que essas respostas às perguntas tenham sido úteis para quem as lê. Agora vamos falar mais sobre useEffect .

Cada renderização possui suas próprias propriedades e estado.


Antes de podermos discutir os efeitos, precisamos falar sobre renderização.

Aqui está o componente do contador funcional.

 function Counter() { const [count, setCount] = useState(0); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

Dê uma olhada na <p>You clicked {count} times</p> . O que ela quer dizer? A constante constante de alguma forma "observa" as mudanças no estado e é atualizada automaticamente? Essa conclusão pode ser considerada uma espécie de primeira ideia valiosa de alguém que está estudando o React, mas não é um modelo mental preciso do que está acontecendo.

No nosso exemplo, count é apenas um número. Isso não é algum tipo de "ligação de dados" mágica, não é algum tipo de "objeto observador" ou "proxy", ou qualquer outra coisa. À nossa frente está um bom número antigo, assim:

 const count = 42; // ... <p>You clicked {count} times</p> // ... 

Durante a saída do primeiro componente, o valor da count obtido de useState() é 0. Quando chamamos setCount(1) , o React chama o componente novamente. Essa count tempo será 1. E assim por diante:

 //     function Counter() { const count = 0; //  useState() // ... <p>You clicked {count} times</p> // ... } //       function Counter() { const count = 1; //  useState() // ... <p>You clicked {count} times</p> // ... } //        function Counter() { const count = 2; //  useState() // ... <p>You clicked {count} times</p> // ... } 

React chama o componente sempre que atualizarmos o estado. Como resultado, cada operação de renderização “vê” seu próprio valor do estado do counter , que, dentro da função, é uma constante.

Como resultado, esta linha não executa nenhuma operação especial de ligação de dados:

 <p>You clicked {count} times</p> 

Ele incorpora apenas um valor numérico no código gerado durante a renderização. Este número é fornecido pelo React. Quando chamamos setCount , o React chama o componente novamente com um valor de count diferente. O React atualiza o DOM para que o modelo de objeto do documento corresponda à saída de dados mais recente durante a renderização do componente.

A conclusão mais importante que se pode tirar disso é que a count é uma constante dentro de qualquer renderização específica e não muda com o tempo. O componente que é chamado repetidamente muda. Cada renderização "vê" seu próprio valor de count , que é isolado para cada uma das operações de renderização.

Neste material, você pode encontrar detalhes sobre esse processo.

Cada renderização possui seus próprios manipuladores de eventos.


Tudo ainda está claro. E os manipuladores de eventos?
Dê uma olhada neste exemplo. Aqui, três segundos depois de clicar no botão, uma caixa de mensagem é exibida com informações sobre o valor armazenado na count :

 function Counter() { const [count, setCount] = useState(0); function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>     <button onClick={handleAlertClick}>       Show alert     </button>   </div> ); } 

Suponha que eu execute a seguinte sequência de ações:

  • Trarei o valor da count para 3 clicando no botão Click me .
  • Clique no botão Show alert .
  • Aumente o valor para 5 antes que o tempo limite expire.


Aumentando o valor da contagem depois de clicar no botão Mostrar alerta

O que você acha que aparece na caixa de mensagem? Será exibido 5 lá, que corresponde ao valor da count no momento em que o timer foi acionado, ou 3 - ou seja, o valor da count no momento em que o botão for pressionado?

Agora você encontrará a resposta para esta pergunta, mas se quiser descobrir tudo sozinho - aqui está uma versão funcional deste exemplo.

Se o que você viu lhe parece incompreensível - aqui está um exemplo mais próximo da realidade. Imagine um aplicativo de bate-papo no qual, no estado, o ID destinatário atual da mensagem esteja armazenado e haja um botão Send . Neste material, o que está acontecendo é considerado em detalhes. De fato, a resposta correta para a pergunta do que aparece na caixa de mensagem é 3.

O mecanismo para exibir uma caixa de mensagem "capturou" o estado no momento em que o botão foi clicado.

Existem maneiras de implementar outra versão do comportamento, mas, por enquanto, trataremos do comportamento padrão do sistema. Ao construir modelos mentais de tecnologias, é importante distinguir o "caminho de menor resistência" de todos os tipos de "saídas de emergência".

Como tudo isso funciona?

Já dissemos que o valor da count é uma constante para cada chamada específica à nossa função. Eu acho que vale a pena pensar nisso com mais detalhes. O ponto é que nossa função é chamada várias vezes (uma vez para cada operação de renderização), mas com cada uma dessas chamadas, a count interna é uma constante. Essa constante é configurada para algum valor específico (representando o estado de uma operação de renderização específica).

Esse comportamento das funções não é algo especial para o React - as funções comuns se comportam de maneira semelhante:

 function sayHi(person) { const name = person.name; setTimeout(() => {   alert('Hello, ' + name); }, 3000); } let someone = {name: 'Dan'}; sayHi(someone); someone = {name: 'Yuzhi'}; sayHi(someone); someone = {name: 'Dominic'}; sayHi(someone); 

Neste exemplo, a variável externa someone reatribuída várias vezes. A mesma coisa pode acontecer em algum lugar dentro do React, o estado atual do componente pode mudar. No entanto, dentro da função sayHi , há um name constante local associado à person de uma chamada específica. Esta constante é local, portanto, seus valores em diferentes chamadas de função são isolados um do outro! Como resultado, após um tempo limite, cada janela de mensagem exibida “lembra” seu próprio valor de name .

Isso explica como nosso manipulador de eventos captura o valor da count quando um botão é clicado. Se nós, trabalhando com componentes, aplicamos o mesmo princípio, acontece que cada renderização "vê" seu próprio valor de count :

 //     function Counter() { const count = 0; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } //       function Counter() { const count = 1; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } //        function Counter() { const count = 2; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } 

Como resultado, cada renderização, de fato, retorna sua própria "versão" handleAlertClick . Cada uma dessas versões "lembra" seu próprio valor de count :

 //     function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 0);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   0 // ... } //       function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 1);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   1 // ... } //        function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 2);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   2 // ... } 

É por isso que , neste exemplo, os manipuladores de eventos "pertencem" a renderizações específicas e, quando você clica no botão, o componente usa o estado de count dessas renderizações.

Dentro de cada renderização específica, as propriedades e o estado sempre permanecem os mesmos. Mas se diferentes operações de renderização usarem suas próprias propriedades e estado, o mesmo acontecerá com qualquer mecanismo que as utilize (incluindo manipuladores de eventos). Eles também "pertencem" a renderizações específicas. Portanto, mesmo funções assíncronas dentro de manipuladores de eventos “verão” os mesmos valores de count .

Deve-se notar que, no exemplo acima, eu handleAlertClick os valores de count específicos diretamente na função handleAlertClick . Essa substituição "mental" não nos prejudicará, pois a count constante não pode ser alterada dentro de uma renderização específica. Em primeiro lugar, é uma constante e, em segundo lugar, é um número. Podemos dizer com confiança que também se pode pensar em outros significados, como objetos, mas apenas se aceitarmos como regra não fazer alterações (mutações) do estado. Ao mesmo tempo, estamos satisfeitos com a chamada para setSomething(newObj) com um novo objeto, em vez de alterar o existente, pois, com essa abordagem, o estado pertencente à renderização anterior permanece intocado.

Cada renderização tem seus próprios efeitos.


Esse material, como você sabe, é dedicado aos efeitos, mas ainda nem falamos sobre eles. Agora vamos consertar isso. Como se vê, trabalhar com efeitos não é particularmente diferente do que já descobrimos.

Considere um exemplo da documentação, que é muito semelhante ao que já analisamos:

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   document.title = `You clicked ${count} times`; }); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

Agora eu tenho uma pergunta para você. Como um efeito lê o valor mais recente da count ?

Talvez alguma "ligação de dados" seja usada aqui, ou um "objeto observador" que atualize o valor da count dentro da função de efeito? Talvez count seja uma variável mutável cujo valor React defina dentro de nosso componente, como resultado do qual o efeito sempre vê sua versão mais recente?

Não.

Já sabemos que, na renderização de um componente em particular, a count é uma constante. Até os manipuladores de eventos "veem" o valor da count da renderização à qual eles "pertencem" devido ao fato de que a count é uma constante localizada em um determinado escopo. O mesmo vale para efeitos!

E deve-se notar que essa não é a count variáveis count que de alguma forma muda dentro do efeito "inalterado". À nossa frente está a função do próprio efeito, que é diferente em cada operação de renderização.

Cada versão "vê" o valor da count da renderização à qual "pertence":

 //     function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${0} times`;   } ); // ... } //       function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${1} times`;   } ); // ... } //        function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${2} times`;   } ); // .. } 

React , DOM .

, ( ), , , , «» , «».

, , .

, ( , ). , , , .

, , :

React:

  • , 0.

:

  • : <p>You clicked 0 times</p> .
  • , , : () => { document.title = 'You clicked 0 times' } .

React:

  • . . , , - DOM.

:

  • , .

React:

  • , , .
  • () => { document.title = 'You clicked 0 times' } .

, . , , - :

:

  • , React, 1.

React:

  • , 1.

:

  • : <p>You clicked 1 times</p> .
  • , , : () => { document.title = 'You clicked 1 times' } .

React:

  • . . , , - DOM.

:

  • , .

React:

  • , , .
  • () => { document.title = 'You clicked 1 times' } .


, , , , «» .

. :

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   setTimeout(() => {     console.log(`You clicked ${count} times`);   }, 3000); }); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

, ?

, . , , , . ! , , , , , count . .




: «, ! ?».

, , this.setState , , . , , , , , :

   componentDidUpdate() {   setTimeout(() => {     console.log(`You clicked ${this.state.count} times`);   }, 3000); } 

, this.state.count count , , . , , , 5 , 5 .




, JavaScript-, , , , , setTimeout , . , (React this.state , ), .

— , , «» , . , , , . , , . , , , , , , .


, ( , , - API ) , .

:

 function Example(props) { useEffect(() => {   setTimeout(() => {     console.log(props.counter);   }, 1000); }); // ... } function Example(props) { const counter = props.counter; useEffect(() => {   setTimeout(() => {     console.log(counter);   }, 1000); }); // ... } 

, «» . ! . , .

, , - , , , , . , ref , .

, , , , , . , ( ), «» React-. , , . , .

, , , :

 function Example() { const [count, setCount] = useState(0); const latestCount = useRef(count); useEffect(() => {   //        count   latestCount.current = count;   setTimeout(() => {     //            console.log(`You clicked ${latestCount.current} times`);   }, 3000); }); // ... 




- React . React this.state . , , latestCount.current . , . , , , .

?


, . , , «» .

:

 useEffect(() => {   ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);   return () => {     ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);   }; }); 

, props{id: 10} , {id: 20} — . , :

  • React {id: 10} .
  • React {id: 20} .
  • React {id: 20} .

( , , .)

, «» - , , «» - , . — , , , . .

React , . , . . . :

  • React {id: 20} .
  • . {id: 20} .
  • React {id: 10} .
  • React {id: 20} .

, «» props , {id: 10} , , props {id: 20} .

, …


— ?

: « ( , , - API ) , ».

! « » , . , , :

 //  ,  props  {id: 10} function Example() { // ... useEffect(   //       () => {     ChatAPI.subscribeToFriendStatus(10, handleStatusChange);     //           return () => {       ChatAPI.unsubscribeFromFriendStatus(10, handleStatusChange);     };   } ); // ... } //  ,  props  {id: 20} function Example() { // ... useEffect(   //       () => {     ChatAPI.subscribeToFriendStatus(20, handleStatusChange);     //           return () => {       ChatAPI.unsubscribeFromFriendStatus(20, handleStatusChange);     };   } ); // ... } 

, , … , «» , -, {id: 10} .

React . , , . props , .

,


React , . .

, :

 function Greeting({ name }) { return (   <h1 className="Greeting">     Hello, {name}   </h1> ); } 

, <Greeting name="Dan" /> , — <Greeting name="Yuzhi" /> , <Greeting name="Yuzhi" /> . Hello, Yuzhi .

, , . React, . , , . $.addClass $.removeClass jQuery- ( — , «»), , CSS- React ( — , «»).

React DOM , . «» «».

. useEffect , React, .

 function Greeting({ name }) { useEffect(() => {   document.title = 'Hello, ' + name; }); return (   <h1 className="Greeting">     Hello, {name}   </h1> ); } 

useEffect , , . , - , ! , «», «».

, A , B , — C , , C . (, - ), .

, , , . ( ).

?

React


React DOM. DOM , React DOM, - .

, :

 <h1 className="Greeting"> Hello, Dan </h1> 

:

 <h1 className="Greeting"> Hello, Yuzhi </h1> 

React :

 const oldProps = {className: 'Greeting', children: 'Hello, Dan'}; const newProps = {className: 'Greeting', children: 'Hello, Yuzhi'}; 

React , children , DOM. , className . :

 domNode.innerText = 'Hello, Yuzhi'; // domNode.className    

- ? , , .

, , - :

 function Greeting({ name }) { const [counter, setCounter] = useState(0); useEffect(() => {   document.title = 'Hello, ' + name; }); return (   <h1 className="Greeting">     Hello, {name}     <button onClick={() => setCounter(counter + 1)}>       Increment     </button>   </h1> ); } 

counter . document.title name , name . document.title counter , .

React … ?

 let oldEffect = () => { document.title = 'Hello, Dan'; }; let newEffect = () => { document.title = 'Hello, Dan'; }; //   React  ,        ? 

— . React , , . ( . name .)

, , ( deps ), useEffect :

   useEffect(() => {   document.title = 'Hello, ' + name; }, [name]); //   

, React: «, , , , name ».

, , React :

 const oldEffect = () => { document.title = 'Hello, Dan'; }; const oldDeps = ['Dan']; const newEffect = () => { document.title = 'Hello, Dan'; }; const newDeps = ['Dan']; // React     ,     . //      ,     . 

, , ! - - .

React


React — . , , , , useEffect , , , . ( !)

 function SearchResults() { async function fetchData() {   // ... } useEffect(() => {   fetchData(); }, []); //   ?  .      . // ... } 

FAQ , . .

« !», — . : , , . , , , — , .

, , . , , , , . , . .

, , .

, React


, , React , .

   useEffect(() => {   document.title = 'Hello, ' + name; }, [name]); 




, , , [] , , , , :

   useEffect(() => {   document.title = 'Hello, ' + name; }, []); // :    name 




. , «» , , .

, , , . , : « setInterval clearInterval ». . , , , useEffect , , , [] . - , ?

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, []); return <h1>{count}</h1>; } 

, , .

, « , », . , , , setInterval , . , ?

, — React , , . , count , React , , , . — .

count 0. setCount(count + 1) setCount(0 + 1) . , — [] , setCount(0 + 1) :

 //  ,   0 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(0 + 1); //  setCount(1)     }, 1000);     return () => clearInterval(id);   },   [] //    ); // ... } //       1 function Counter() { // ... useEffect(   //     - ,    //   React  ,   .   () => {     const id = setInterval(() => {       setCount(1 + 1);     }, 1000);     return () => clearInterval(id);   },   [] ); // ... } 

React, , , — .

count — , ( ):

   const count = // ... useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, []); 

. React .


,

. , , React , , . — - .


React , . , , , .

, , , . count :

 useEffect(() => { const id = setInterval(() => {   setCount(count + 1); }, 1000); return () => clearInterval(id); }, [count]); 

. , , — , . count , count , setCount(count + 1) :

 //  ,   0 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(0 + 1); // setCount(count + 1)     }, 1000);     return () => clearInterval(id);   },   [0] // [count] ); // ... } //  ,   1 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(1 + 1); // setCount(count + 1)     }, 1000);     return () => clearInterval(id);   },   [1] // [count] ); // ... } 

, setInterval , count , . , .


,

, , , . — , .

.


, count .

 useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, [count]); 

, , count . , count setCount . , , count . , , setState :

   useEffect(() => {   const id = setInterval(() => {     setCount(c => c + 1);   }, 1000);   return () => clearInterval(id); }, []); 

« ». , count - , setCount(count + 1) . count - , count + 1 «» React. React count . , React — , , , .

setCount(c => c + 1) . « React », , . « » , , .

, , , . React. count :


,

.

, setInterval , , c => c + 1 . count . React .

Google Docs


, , — ? , , «», , . , Google Docs . . , .

, . . , setCount(c => c + 1) , , setCount(count + 1) , «» count . , ( — «»). « React» — . .

( ) , Google Docs . — , React . , , ( , , ) .

, setCount(c => c + 1) , . , . , , , , , . setCount(c => c + 1) . useReducer .


, : count step . setInterval , step :

 function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1); useEffect(() => {   const id = setInterval(() => {     setCount(c => c + step);   }, 1000);   return () => clearInterval(id); }, [step]); return (   <>     <h1>{count}</h1>     <input value={step} onChange={e => setStep(Number(e.target.value))} />   </> ); } 

.

, React . step , . .

: step setIntervalstep . , , , ! , , , , , .

, , , setInterval , step . step ?

, , useReducer .

, setSomething(something => ...) , , . «», , , .

step dispatch :

 const [state, dispatch] = useReducer(reducer, initialState); const { count, step } = state; useEffect(() => { const id = setInterval(() => {   dispatch({ type: 'tick' }); //  setCount(c => c + step); }, 1000); return () => clearInterval(id); }, [dispatch]); 

.

: « , ?». , React , dispatch . .

!

( dispatch setstate useRef , React , . — .)

, , , , . step . , . , . :

 const initialState = { count: 0, step: 1, }; function reducer(state, action) { const { count, step } = state; if (action.type === 'tick') {   return { count: count + step, step }; } else if (action.type === 'step') {   return { count, step: action.step }; } else {   throw new Error(); } } 

, , , .

useReducer — -


, , , . , , ? , , API <Counter step={1} /> . , props.step ?

, ! , :

 function Counter({ step }) { const [count, dispatch] = useReducer(reducer, 0); function reducer(state, action) {   if (action.type === 'tick') {     return state + step;   } else {     throw new Error();   } } useEffect(() => {   const id = setInterval(() => {     dispatch({ type: 'tick' });   }, 1000);   return () => clearInterval(id); }, [dispatch]); return <h1>{count}</h1>; } 

, . , , , , . .

dispatch . , , . .

, , . «» , , ? , dispatch , React . . .

useReducer «-» . , . , , , , .


, - , .

, , , :

 function SearchResults() { const [data, setData] = useState({ hits: [] }); async function fetchData() {   const result = await axios(     'https://hn.algolia.com/api/v1/search?query=react',   );   setData(result.data); } useEffect(() => {   fetchData(); }, []); //   ? // ... 

, .

, , . , , , , , , , , .

, , , , :

 function SearchResults() { // ,       function getFetchUrl() {   return 'https://hn.algolia.com/api/v1/search?query=react'; } // ,        async function fetchData() {   const result = await axios(getFetchUrl());   setData(result.data); } useEffect(() => {   fetchData(); }, []); // ... } 

, , :

 function SearchResults() { const [query, setQuery] = useState('react'); // ,       function getFetchUrl() {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } // ,        async function fetchData() {   const result = await axios(getFetchUrl());   setData(result.data); } useEffect(() => {   fetchData(); }, []); // ... } 

, (, ), . .

, . , :

 function SearchResults() { // ... useEffect(() => {   //      !   function getFetchUrl() {     return 'https://hn.algolia.com/api/v1/search?query=react';   }   async function fetchData() {     const result = await axios(getFetchUrl());     setData(result.data);   }   fetchData(); }, []); //    . // ... } 

.

? , « ». React, - .

getFetchUrl , query , , , , . — , query :

 function SearchResults() { const [query, setQuery] = useState('react'); useEffect(() => {   function getFetchUrl() {     return 'https://hn.algolia.com/api/v1/search?query=' + query;   }   async function fetchData() {     const result = await axios(getFetchUrl());     setData(result.data);   }   fetchData(); }, [query]); //    . // ... } 

.

, « React». query . , , , , . , , .

exhaustive-deps eslint-plugin-react-hooks , . , , .




.

, ?


. , , . , , .

? , . : React . . , « ». , , . , , , !

, , . , getFetchUrl :

 function SearchResults() { function getFetchUrl(query) {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, []); //  : getFetchUrl useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, []); //  : getFetchUrl // ... } 

getFetchUrl — , .

, «» , . getFetchUrl (, , ), :

 function SearchResults() { //       function getFetchUrl(query) {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => {   const url = getFetchUrl('react');   // ...    -    ... }, [getFetchUrl]); //   ,     . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, [getFetchUrl]); //   ,     . // ... } 

, getFetchUrl . , — . - , , . , , , .

— .

, , :

 //        function getFetchUrl(query) { return 'https://hn.algolia.com/api/v1/search?query=' + query; } function SearchResults() { useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, []); //     . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, []); //     . // ... } 

, . , , .

. , useCallback :

 function SearchResults() { //    ,   const getFetchUrl = useCallback((query) => {   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []);  //      . useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, [getFetchUrl]); //      . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, [getFetchUrl]); //       // ... } 

useCallback . : , -, , , .

, . ( 'react' 'redux' ). , , , query . , , query , getFetchUrl .

, query useCallback :

 function SearchResults() { const [query, setQuery] = useState('react'); const getFetchUrl = useCallback(() => { //   query   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []); //  : query // ... } 

useCallback query , , getFetchUrl , query :

 function SearchResults() { const [query, setQuery] = useState('react'); //      query const getFetchUrl = useCallback(() => {   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, [query]);  //    . useEffect(() => {   const url = getFetchUrl();   // ...    -   ... }, [getFetchUrl]); //    . // ... } 

useCallback , query , getFetchUrl , , . query , getFetchUrl , . Excel: - , , , .

— , . , :

 function Parent() { const [query, setQuery] = useState('react'); //      query const fetchData = useCallback(() => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + query;   // ...      ... }, [query]);  //       return <Child fetchData={fetchData} /> } function Child({ fetchData }) { let [data, setData] = useState(null); useEffect(() => {   fetchData().then(setData); }, [fetchData]); //       // ... } 

fetchData Parent query , Child , .

?


, , , , . , , , , :

 class Parent extends Component { state = {   query: 'react' }; fetchData = () => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;   // ...    -   ... }; render() {   return <Child fetchData={this.fetchData} />; } } class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } render() {   // ... } } 

, : « , , , useEffectcomponentDidMount componentDidUpdate . !». componentDidUpdate :

 class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } componentDidUpdate(prevProps) {   //         if (this.props.fetchData !== prevProps.fetchData) {     this.props.fetchData();   } } render() {   // ... } } 

, fetchData — ! (, , , .) - , . this.props.fetchData prevProps.fetchData . , , ?

   componentDidUpdate(prevProps) {   this.props.fetchData(); } 

. . ( .) , fetchData this.state.query ?

   render() {   return <Child fetchData={this.fetchData.bind(this, this.state.query)} />; } 

this.props.fetchData !== prevProps.fetchData true , , query ! .

, , , query Child . , , query , query :

 class Parent extends Component { state = {   query: 'react' }; fetchData = () => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;   // ...    -    ... }; render() {   return <Child fetchData={this.fetchData} query={this.state.query} />; } } class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } componentDidUpdate(prevProps) {   if (this.props.query !== prevProps.query) {     this.props.fetchData();   } } render() {   // ... } } 

, , - , , .

, , . this , . , , , , - . , this.props.fetchData , , , , , .

- useCallback . , , , . , . useCallback props.fetchData .

, useMemo :

 function ColorPicker() { //         Child, //       . const [color, setColor] = useState('pink'); const style = useMemo(() => ({ color }), [color]); return <Child style={style} />; } 

, useCallback , - . « », , , . , . , .

, fetchData ( ), . , , . (« props.onComplete , ?») , .


, :

 class Article extends Component { state = {   article: null }; componentDidMount() {   this.fetchData(this.props.id); } async fetchData(id) {   const article = await API.fetchArticle(id);   this.setState({ article }); } // ... } 

, , , . . — , :

 class Article extends Component { state = {   article: null }; componentDidMount() {   this.fetchData(this.props.id); } componentDidUpdate(prevProps) {   if (prevProps.id !== this.props.id) {     this.fetchData(this.props.id);   } } async fetchData(id) {   const article = await API.fetchArticle(id);   this.setState({ article }); } // ... } 

, , , . , . , {id: 10} , {id: 20} , , . , , , . E isso está errado.

, , . — , , async/await ( , - ) , ( , ).

, async -. (, , , , .)

, , ! .

, :

 function Article({ id }) { const [article, setArticle] = useState(null); useEffect(() => {   let didCancel = false;   async function fetchData() {     const article = await API.fetchArticle(id);     if (!didCancel) {       setArticle(article);     }   }   fetchData();   return () => {     didCancel = true;   }; }, [id]); // ... } 

, , , . , .


, , , , , . , , , . . — .

useEffect , , , . React. , useEffect .

, , « », . . , , , , «» , .

, useEffect , . — . — , , — , . , , , , API.

, , useFetch , , useTheme , . , , useEffect . , , , .

, , useEffect . — , . , . ?

Suspense React , , - ( : , , ) .

Suspense , , useEffect , , , - . , , , . , , , , .

Sumário


, . , , - , , , .

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


All Articles