React Hooks: Não é mágica, apenas arrays

Com diagramas para explicar as regras dessa nova abordagem

Não existe mágica, são apenas arrays! — Créditos imagem

Eu sou um grande fã da nova API Hooks. No entanto, tem algumas restrições estranhas sobre como você deva usá-la. Para aqueles que estão lutando para entender as razões dessas novas regras, vou apresentar um modelo de como pensar ao utilizar essa nova API.

Entendendo como Hooks funcionam

Eu ouvi algumas pessoas gritando que é ‘mágica’ em torno da proposta da nova API. Pensei em tentar descompactar como a proposta da sintaxe funciona, pelo menos em um nível superficial.

As regras dos Hooks

Existem duas regras principais que a equipe do React estipula, e você precisa seguir para poder utilizá-los, e foram descritas na documentação de proposta dos Hooks.

  • Não chame Hooks dentro de loops, condições ou funções
  • Utilize Hooks apenas de dentro de funções React

Este último eu acho que é evidente. Para anexar o comportamento a um componente funcional, você precisa ser capaz de associar esse comportamento ao componente de alguma forma.

O primeiro, no entanto, acho que pode ser confuso, pois pode parecer pouco natural programar usando uma API como essa e é isso que eu quero explorar hoje.

Arrays, arrays e arrays! Gerenciamento de estado em Hooks é tudo sobre arrays!

Para obter um modelo mental mais claro, vamos dar uma olhada em como uma implementação simples da API Hooks pode parecer.

Por favor, note que isso é especulação e apenas um exemplo de implementação da API para mostrar como você pode pensar sobre isso. Isso não é necessariamente como a API funciona internamente. Além disso, esta é apenas uma proposta. Tudo isso pode mudar no futuro.

Como poderíamos implementar useState()?

Vamos descompactar um exemplo aqui para demonstrar como uma implementação de gerenciamento de estado pode funcionar.

Primeiro vamos começar com um componente:

function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}

A idéia por trás da API Hooks é que você pode usar uma função setter retornada como o segundo item do array da função do Hooks e que o setter controlará o estado que é gerenciado internamente.

Então, o que o React fará com isso?

Vamos anotar como isso pode funcionar internamente no React. O seguinte diagrama, iria funcionar dentro do contexto de execução para renderizar um componente em particular. Isso significa que os dados armazenados aqui vivem um nível fora do componente que está sendo processado. Esse estado não é compartilhado com outros componentes, mas é mantido em um escopo que é acessível para a renderização subsequente do componente específico.

1) Inicialização

Crie dois arrays vazios: setters e state. Vamos definir um cursor para 0:

Inicialização: Duas matrizes vazias, Cursor é 0
Inicialização: Duas matrizes vazias, Cursor é 0

2) Primeira renderização

Executamos a função do componente pela primeira vez.

Cada chamada ao useState(), quando executada pela primeira vez, envia uma função setter (ligada a uma posição do cursor) no array setters e, em seguida, envia algum estado para o array state.

Primeira renderização: Itens gravados nos arrays como incrementos do cursor.
Primeira renderização: Itens gravados nos arrays como incrementos do cursor.

3) Renderização subseqüente

Cada renderização subseqüente, o cursor é redefinido e esses valores são apenas lidos de cada array.

Renderização subseqüente: itens lidos do array como incrementos de cursor
Renderização subseqüente: itens lidos do array como incrementos de cursor

4) Manipulação de eventos

Cada setter tem uma referência à sua posição do cursor, portanto, ao acionar a chamada para qualquer um, o setter irá alterar o valor do estado na posição do array state.

Os setters “lembram” o seu índice e ajustam a memória de acordo com ele.
Os setters “lembram” o seu índice e ajustam a memória de acordo com ele.

E nossa implementação ingênua…

Aqui está um exemplo de código para demonstrar essa implementação:

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
// Pseudo-código para uma função `useState`
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
  const setter = setters[cursor];
const value = state[cursor];
  cursor++;
return [value, setter];
}
// Um exemplo de componente usando Hooks
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
  return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
// Uma espécie de simulação do ciclo de renderização do React
function MyComponent() {
cursor = 0; // redefinindo o cursor
return <RenderFunctionComponent />; // render
}
console.log(state); // Pré-renderização: []
MyComponent();
console.log(state); // Primeira renderização: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Renderizações subsequentes: ['Rudi', 'Yardley']
// clique no botão 'Fred'
console.log(state); // Estado após o evento `click`: ['Fred', 'Yardley']

Por que a ordem é importante?

Agora, o que acontece se mudarmos a ordem dos Hooks para um ciclo de renderização baseado em algum fator externo ou mesmo no estado do componente?

Vamos fazer o que a equipe do React diz que você não deve fazer:

let firstRender = true;
function RenderFunctionComponent() {
let initName;

if(firstRender){
[initName] = useState("Rudi");
firstRender = false;
}
const [firstName, setFirstName] = useState(initName);
const [lastName, setLastName] = useState("Yardley");
  return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}

Aqui nós temos uma chamada para useState em uma condicional. Vamos ver o caos que isso cria no sistema.

Primeira renderização ruim para o componente

Renderizando um Hook extra 'ruim' que será eliminado na próxima renderização
Renderizando um Hook extra ‘ruim’ que será eliminado na próxima renderização

Neste ponto, nossas instâncias de firstName e lastName contém os dados corretos, mas vamos dar uma olhada no que acontece no segundo render:

Renderizações subsequentes são piores

Ao remover o gancho entre os processos de renderização, obtemos um erro.
Ao remover o hook entre os processos de renderização, obtemos um erro.

Agora ambos firstName e lastName estão definidos como “Rudi” no nosso armazenamento de estado, tornando-se inconsistente. Isso é claramente errôneo e não funciona, mas nos dá uma idéia de por que as regras para Hooks são apresentadas do jeito que são.

A equipe do React está estipulando as regras de uso porque resultará em dados inconsistentes ao utilizar Hooks!

Pense em Hooks como uma manipulação de um conjunto de arrays, e você não quebrará as regras!

Portanto, agora deve estar claro por que você não pode chamar Hooks use* dentro de condicionais ou loops. Como estamos lidando com um cursor apontando para um conjunto de arrays, se você alterar a ordem das chamadas dentro da renderização, o cursor não corresponderá aos dados e suas chamadas de uso não apontarão para os dados ou manipuladores corretos.

Portanto, o truque é pensar que Hooks estão gerenciando sua aplicação como um conjunto de arrays que precisam de um cursor consistente. Se você fizer isso tudo deve funcionar.

Conclusão

Espero ter apresentado um modelo mental mais claro de como pensar sobre o que está acontecendo sob o capô com a nova API. Lembre-se de que o verdadeiro valor aqui é poder agrupar as preocupações, portanto, ter cuidado com a ordem e usar a API corretamente terá um alto retorno.

Hooks é uma API de plug-in efetiva para componentes do React. Existe uma razão pela qual as pessoas estão entusiasmadas com isso e se você pensar sobre esse tipo de modelo, onde o estado existe como um conjunto de arrays, então você não deve encontrar muitos problemas, bastar seguir as regras em torno de seu uso.

Espero dar uma olhada no método useEffects no futuro e tentar compará-lo aos métodos de ciclo de vida de componentes do React.

Você pode seguir Rudi Yardley no Twitter como @rudiyardley ou no Github como @ryardley

⭐️ Créditos