Introdução ao React, parte 3

Publicados: 2020-07-30

Na semana passada vimos que um componente React nada mais é do que a representação gráfica de (parte de) nosso estado de aplicação. No exemplo que implementamos, um componente mostrava o valor de um contador junto com dois botões que permitiam aumentar ou diminuir esse valor. O truque era simples: as props de um componente podem ser tanto dados quanto funções.

Até agora, as duas ideias principais que aprendemos na Parte 1 e na Parte 2 deste tutorial são:

  • Um componente React é uma função pura que recebe um conjunto de propriedades e gera o HTML necessário para renderizá-lo.
  • As propriedades que um componente React recebe podem ser (a) os dados que ele deve mostrar na interface do usuário e (b) funções que, uma vez conectadas como callbacks a eventos DOM, modificam o estado do nosso aplicativo.

Bem, nesta terceira parte vamos ampliar um pouco mais nosso conhecimento sobre essa arquitetura e vamos aprender a usar as lojas WordPress . Com eles, podemos definir o estado do nosso aplicativo de maneira limpa, testável e independente da interface do usuário.

Estendendo nosso exemplo

Na segunda parte deste tutorial criamos um contador com alguns botões para aumentar ou diminuir seu valor. Para fazer isso, adicionamos uma variável mutável no arquivo index.js junto com uma função para alterá-la. Nosso componente Contador :

 export const Counter = ( { value, onIncrease, onDecrease } ) => ( <div> <div>Counter: <strong>{ value }</strong></div> <button onClick={ onIncrease }>+</button> <button onClick={ onDecrease }>-</button> </div> );

poderia então receber todos os adereços necessários:

 // Import dependencies import { render } from '@wordpress/element'; import { Counter } from './components/counter'; // Store value let value = 0; function setValue( newValue ) { value = newValue; } // Render component in DOM const wrapper = document.getElementById( 'react-example-wrapper' ); render( <Counter value={ value } onIncrease={ () => setValue( value + 1 ) } onDecrease={ () => setValue( value - 1 ) } />, wrapper );

Bem, hoje vamos aprender como podemos implementar a gestão estadual de forma adequada e melhor. Mas primeiro, deixe-me apontar para um exemplo mais complexo: vamos expandir nosso aplicativo para que ele possa ter vários contadores ao mesmo tempo e ver como podemos gerenciá-los usando uma loja WordPress. O resultado final (que implementaremos no próximo post) será semelhante a este:

Vários contadores com React e Redux
Vários contadores implementados com componentes React e uma loja WordPress baseada em Redux.

Definindo o estado do nosso aplicativo

Ao enfrentar um problema como esse, a primeira coisa que você deve pensar é como você vai gerenciar o estado do seu aplicativo . E, supondo que você só possa ler e escrever o estado usando funções, a maneira mais fácil de fazer isso é pensar em sua API: ou seja, as funções que permitem que você o consulte e atualize . No nosso caso, sugiro:

  • Adicionando um novo contador
  • Excluindo contador x
  • Defina o valor do contador x
  • Obtenha uma lista de todos os contadores que nosso aplicativo possui
  • Obter o valor do contador x

mas você pode querer usar uma abordagem diferente (por exemplo, você pode querer criar uma função para incrementar um determinado contador e uma função diferente para decrementá-lo).

Depois de definir essa interface, você deve pensar nas informações necessárias para acompanhar esse estado e, em particular, na estrutura de dados que usará para fazer isso. Em nosso exemplo, queremos acompanhar vários contadores e, para cada contador x , queremos saber seu valor. Quais estruturas de dados podem nos ajudar nessa empreitada?

Dependendo do que é esse x , você pode querer usar uma estrutura de dados ou outra. Se, por exemplo, x for o índice do contador (ou seja, você deseja recuperar o valor do primeiro , segundo ou terceiro contador), uma matriz de números pode ser suficiente. Se você quiser que x seja um identificador de contador exclusivo, talvez queira usar uma abordagem diferente. Por exemplo, você pode querer usar um dicionário com pares id/valor:

 const counters = { ae13a: 0, f18bb: 3, e889a: 1, 8b1d3: -5, };

ou uma matriz de objetos:

 const counters = [ { id: 'ae13a', value: 0 }, { id: 'f18bb', value: 3 }, { id: 'e889a', value: 1 }, { id: '8b1d3', value: -5 }, ];

Se você primeiro definir a interface (setters e getters, se desejar) para manipular e acessar o estado do seu aplicativo, então como você implementa o próprio estado é uma caixa preta. Isso significa que você pode alterar a própria loja a qualquer momento e as coisas funcionarão conforme o esperado, desde que você mantenha a API.

Criando uma loja baseada em Redux com WordPress

O módulo de dados do WordPress permite definir uma ou mais lojas para gerenciar o estado do seu aplicativo. Para criar uma nova loja usando este pacote, basta usar a função registerStore com os seguintes argumentos:

  1. Um nome que identifica exclusivamente sua loja
  2. Um objeto selectors com todos os getters para recuperar dados da loja
  3. Um objeto de actions com função que aciona solicitações de atualização (falaremos sobre o que isso significa mais adiante neste post)
  4. Uma função reducer responsável por atualizar o estado quando certas ações são acionadas

Vamos ver um exemplo real, certo? Continuando com o exemplo da semana passada, crie uma nova pasta de store em src . Em seguida, crie um arquivo index.js nele com o seguinte código:

 // Load dependencies import { registerStore } from '@wordpress/data'; import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; registerStore( 'react-example/counters', { actions, reducer, selectors, } );

O trecho é bastante simples, não é? Simplesmente importamos a função registerStore sobre a qual estávamos falando e algumas dependências que ainda não criamos ( reducer , actions e selectors ), e registramos a nova loja. Observe como nomeamos nossa loja para garantir que ela seja única: combinamos o nome do nosso plugin ( react-example ) com uma palavra que define sobre o que é a loja ( counters ). Mole-mole!

Ações em uma loja

Uma das coisas que (em princípio) toda loja precisa é um conjunto de funções que nos permitem modificar seu estado. Em nosso exemplo, as ações estarão em src/store/actions.js :

 export function addCounter( counterId ) { return { type: 'ADD_COUNTER', counterId, }; } export function removeCounter( counterId ) { return { type: 'REMOVE_COUNTER', counterId, }; } export function setCounterValue( counterId, value ) { return { type: 'SET_COUNTER_VALUE', counterId, value, }; }

Como esperado, nossa loja possui três ações para atualizar seu estado:

  • addCounter : uma função que adiciona novos contadores em nossa loja. O único argumento que leva é o ID do novo contador.
  • removeCounter : uma função que remove um contador existente. Novamente, o único argumento necessário é o ID do contador que desejamos remover.
  • setCounterValue : uma função que define um novo valor para um determinado contador. Obviamente, esta função recebe dois argumentos: o ID do contador a ser atualizado e seu novo valor.

Agora, se você observar atentamente cada ação, poderá se surpreender: nenhuma dessas ações parece atualizar nada. Em vez disso, eles retornam objetos. O que está acontecendo aqui?

No Redux (e as lojas do WordPress são baseadas no Redux), as ações não modificam uma loja diretamente. Em vez disso, eles geram um objeto que sinaliza uma “solicitação de atualização”. Essas solicitações seguem sempre o mesmo padrão: são um objeto com um atributo de type que identifica exclusivamente a solicitação e quantas propriedades adicionais forem necessárias para aplicar com êxito a atualização solicitada.

Então, vamos agora ver como se pode realmente atualizar o estado…

Implementando o redutor para atualizar o estado de uma loja

Se as ações da loja representam apenas uma solicitação de atualização, precisamos que alguém ou algo realmente atualize o estado da nossa loja quando tal solicitação for despachada. É para isso que serve um redutor.

Um redutor é uma função que pega o estado atual do nosso aplicativo e uma ação que alguém despachou e atualiza o estado aplicando a atualização solicitada .

Na seção anterior, vimos que nossa loja possui três ações, portanto, nosso redutor deve ser capaz de aplicar cada uma delas:

 import { omit } from 'lodash'; export default function reducer( state = {}, action ) { switch ( action.type ) { case 'ADD_COUNTER': return { ...state, [ action.counterId ]: 0, }; case 'REMOVE_COUNTER': return omit( state, action.counterId ); case 'SET_COUNTER_VALUE': return { ...state, [ action.counterId ]: action.value, }; } return state; }

Como você pode ver, o redutor recebe o state anterior (que, aliás, por padrão é o objeto vazio {} ) e a ação despachada com as informações para atualizar o estado. O corpo do redutor é bastante simples:

  • Ele começa com uma instrução switch para discernir o tipo de atualização ( action.type ) que devemos executar:
    • Se for ADD_COUNTER , ele gera um novo objeto de state com uma nova chave action.counterId com seu valor de contador definido como 0 .
    • Se for REMOVE_COUNTER , ele gera um novo objeto de state sem a chave action.counterId .
    • Se for SET_COUNTER_VALUE , ele gera um novo objeto de state onde o valor em action.counterId agora é definido como action.value .

O mais importante aqui é perceber que um redutor é (e deve ser) uma função pura . Isso significa que qualquer modificação que fizermos no estado atual envolve a construção de um novo estado . Sob nenhuma circunstância, então, você deve alterar o estado anterior.

Seletores em uma loja

Agora que você sabe como atualizar o estado da sua loja, tudo o que você precisa aprender é como consultá-la. Basta definir um selectors.js com as funções de consulta que você precisa:

 export function getCounterIds( state ) { return Object.keys( state ); } export function getCounterValue( state, counterId ) { return state[ counterId ]; }

e é isso! Bem óbvio, certo? Seletores de loja são funções que recebem (pelo menos) um argumento (o state da loja) e retornam um valor específico . Obviamente, os seletores podem ter mais argumentos se você precisar retornar um valor específico de dentro de sua loja.

No nosso caso, por exemplo, criamos dois seletores:

  • getCounterIds retorna uma matriz de identificadores de contador. Como implementamos a loja usando um dicionário/objeto, estamos simplesmente interessados ​​em retornar seu Object.keys .
  • getCounterValue retorna o valor específico de um determinado contador. Essa função recebe dois argumentos (o state atual do nosso aplicativo e o ID do contador em que estamos interessados) e retorna o valor solicitado.

Como testar nossa loja

Para testar se a loja funciona corretamente, abra o arquivo src/index.js e import -o:

 // Import dependencies import { render } from '@wordpress/element'; import './store'; import { Counter } from './components/counter'; ...

Em seguida, transpile o código (usando npm run build ) e acesse seu navegador. Abra as Ferramentas do desenvolvedor e use o console JavaScript para digitar alguns comandos:

 dispatch = wp.data.dispatch( 'react-example/counters' ); select = wp.data.select( 'react-example/counters' ); dispatch.addCounter( 'a' ); dispatch.addCounter( 'b' ); dispatch.addCounter( 'c' ); dispatch.setCounterValue( 'a', 3 ); select.getCounterIds(); // Array(3) [ "a", "b", "c" ] select.getCounterValue( 'a' ); // 3

Usando as funções de dispatch e select do WordPress, você poderá ver que a loja está funcionando conforme o esperado. E dica bônus: existe uma extensão para Firefox e Chrome chamada Redux DevTools que permite que você veja corretamente suas lojas Redux:

Redux DevTools no Firefox
A extensão Redux DevTools para Firefox e Chrome permite verificar facilmente o estado das lojas Redux.

Próximos passos

A verdade é que o post de hoje foi um pouco mais longo do que eu esperava, então não poderemos ver como podemos usar essa loja para alimentar nossa interface do usuário (ainda). Mas espero que a explicação ajude você a entender como funcionam as lojas do WordPress e como você pode usá-las para gerenciar o estado de seus plugins.

Na próxima semana, seguiremos em frente com o exemplo de hoje e conectaremos nossos componentes React à loja para que (a) o que vemos na interface do usuário seja alimentado pelo estado armazenado na loja e (b) as interações do usuário com a interface do usuário atualizem a loja ( bem como a própria interface do usuário).

Mas, para garantir que você se sinta confortável com tudo o que mostrei hoje, deixe-me propor uma lição de casa: modifique a loja que implementamos hoje para que os dados sejam armazenados da seguinte forma:

 const counters = [ { id: 'ae13a', value: 0 }, { id: 'f18bb', value: 3 }, { id: 'e889a', value: 1 }, { id: '8b1d3', value: -5 }, ];

e já não nos faz um dicionário. Lembre-se de que pode ser necessário atualizar os seletores, ações e redutor para que funcione! Vou compartilhar uma solução na próxima semana

Imagem em destaque por Annie Theby no Unsplash.