Redux - Guia Rápido

Redux é um contêiner de estado previsível para aplicativos JavaScript. Conforme o aplicativo cresce, fica difícil mantê-lo organizado e manter o fluxo de dados. Redux resolve este problema gerenciando o estado da aplicação com um único objeto global chamado Store. Os princípios fundamentais do Redux ajudam a manter a consistência em todo o aplicativo, o que torna a depuração e o teste mais fáceis.

Mais importante ainda, oferece edição de código ao vivo combinada com um depurador que viaja no tempo. É flexível para acompanhar qualquer camada de visão, como React, Angular, Vue, etc.

Princípios do Redux

A previsibilidade do Redux é determinada pelos três princípios mais importantes, conforme abaixo -

Fonte Única da Verdade

O estado de todo o seu aplicativo é armazenado em uma árvore de objetos dentro de um único armazenamento. Como todo o estado do aplicativo é armazenado em uma única árvore, isso torna a depuração fácil e o desenvolvimento mais rápido.

O estado é somente leitura

A única maneira de mudar o estado é emitir uma ação, um objeto que descreve o que aconteceu. Isso significa que ninguém pode alterar diretamente o estado do seu aplicativo.

As mudanças são feitas com funções puras

Para especificar como a árvore de estado é transformada por ações, você escreve redutores puros. Um redutor é um local central onde ocorre a modificação de estado. Redutor é uma função que assume estado e ação como argumentos e retorna um estado recém-atualizado.

Antes de instalar o Redux, we have to install Nodejs and NPM. Abaixo estão as instruções que o ajudarão a instalá-lo. Você pode pular essas etapas se já tiver Nodejs e NPM instalados em seu dispositivo.

  • Visita https://nodejs.org/ e instale o arquivo do pacote.

  • Execute o instalador, siga as instruções e aceite o contrato de licença.

  • Reinicie seu dispositivo para executá-lo.

  • Você pode verificar a instalação bem-sucedida abrindo o prompt de comando e digite node -v. Isso mostrará a versão mais recente do Node em seu sistema.

  • Para verificar se o npm foi instalado com êxito, você pode digitar npm –v que retorna a versão mais recente do npm.

Para instalar o redux, você pode seguir os passos abaixo -

Execute o seguinte comando em seu prompt de comando para instalar o Redux.

npm install --save redux

Para usar o Redux com o aplicativo react, você precisa instalar uma dependência adicional da seguinte forma -

npm install --save react-redux

Para instalar ferramentas de desenvolvedor para Redux, você precisa instalar o seguinte como dependência -

Execute o comando abaixo em seu prompt de comando para instalar Redux dev-tools.

npm install --save-dev redux-devtools

Se você não deseja instalar ferramentas de desenvolvimento Redux e integrá-lo em seu projeto, você pode instalar Redux DevTools Extension para Chrome e Firefox.

Vamos supor que o estado do nosso aplicativo seja descrito por um objeto simples chamado initialState que é o seguinte -

const initialState = {
   isLoading: false,
   items: [],
   hasError: false
};

Cada pedaço de código em seu aplicativo não pode alterar esse estado. Para alterar o estado, você precisa despachar uma ação.

O que é uma ação?

Uma ação é um objeto simples que descreve a intenção de causar mudança com uma propriedade de tipo. Ele deve ter uma propriedade de tipo que informa qual tipo de ação está sendo executada. O comando para ação é o seguinte -

return {
   type: 'ITEMS_REQUEST', //action type
   isLoading: true //payload information
}

Ações e estados são mantidos juntos por uma função chamada Redutor. Uma ação é despachada com a intenção de causar mudança. Essa mudança é realizada pelo redutor. Redutor é a única forma de alterar estados no Redux, tornando-o mais previsível, centralizado e depurável. Uma função redutora que lida com a ação 'ITEMS_REQUEST' é a seguinte -

const reducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}

Redux tem um único armazenamento que mantém o estado do aplicativo. Se você deseja dividir seu código com base na lógica de tratamento de dados, você deve começar a dividir seus redutores em vez de armazená-los no Redux.

Discutiremos como podemos dividir os redutores e combiná-los com a loja mais adiante neste tutorial.

Os componentes Redux são os seguintes -

Redux segue o fluxo de dados unidirecional. Isso significa que os dados do seu aplicativo seguirão em um fluxo de dados de vinculação unilateral. Conforme o aplicativo cresce e se torna complexo, é difícil reproduzir problemas e adicionar novos recursos se você não tiver controle sobre o estado do aplicativo.

Redux reduz a complexidade do código, reforçando a restrição de como e quando a atualização de estado pode acontecer. Dessa forma, é fácil gerenciar os estados atualizados. Já conhecemos as restrições conforme os três princípios do Redux. O diagrama a seguir ajudará você a entender melhor o fluxo de dados Redux -

  • Uma ação é despachada quando um usuário interage com o aplicativo.

  • A função do redutor raiz é chamada com o estado atual e a ação despachada. O redutor raiz pode dividir a tarefa entre funções redutoras menores, o que em última instância retorna um novo estado.

  • A loja notifica a visualização executando suas funções de retorno de chamada.

  • A visualização pode recuperar o estado atualizado e renderizar novamente.

Uma loja é uma árvore de objetos imutáveis ​​no Redux. Uma loja é um contêiner de estado que contém o estado do aplicativo. Redux pode ter apenas uma loja em seu aplicativo. Sempre que uma loja é criada no Redux, você precisa especificar o redutor.

Vamos ver como podemos criar uma loja usando o createStoremétodo do Redux. É necessário importar o pacote createStore da biblioteca Redux que suporta o processo de criação de loja conforme mostrado abaixo -

import { createStore } from 'redux';
import reducer from './reducers/reducer'
const store = createStore(reducer);

Uma função createStore pode ter três argumentos. A seguir está a sintaxe -

createStore(reducer, [preloadedState], [enhancer])

Um redutor é uma função que retorna o próximo estado do aplicativo. Um preloadedState é um argumento opcional e é o estado inicial do seu aplicativo. Um realçador também é um argumento opcional. Isso o ajudará a aprimorar a loja com recursos de terceiros.

Uma loja tem três métodos importantes, conforme mostrado abaixo -

getState

Ele ajuda você a recuperar o estado atual de sua loja Redux.

A sintaxe para getState é a seguinte -

store.getState()

Despacho

Ele permite que você despache uma ação para alterar um estado em seu aplicativo.

A sintaxe para envio é a seguinte -

store.dispatch({type:'ITEMS_REQUEST'})

se inscrever

Isso ajuda você a registrar um retorno de chamada que a loja Redux chamará quando uma ação for despachada. Assim que o estado Redux for atualizado, a visualização será renderizada novamente automaticamente.

A sintaxe para envio é a seguinte -

store.subscribe(()=>{ console.log(store.getState());})

Observe que a função de inscrição retorna uma função para cancelar a inscrição do ouvinte. Para cancelar a inscrição do ouvinte, podemos usar o código abaixo -

const unsubscribe = store.subscribe(()=>{console.log(store.getState());});
unsubscribe();

Ações são a única fonte de informação para a loja conforme documentação oficial do Redux. Ele carrega uma carga útil de informações de seu aplicativo para o armazenamento.

Conforme discutido anteriormente, as ações são objetos JavaScript simples que devem ter um atributo type para indicar o tipo de ação executada. Isso nos conta o que aconteceu. Os tipos devem ser definidos como constantes de string em sua aplicação, conforme mostrado abaixo -

const ITEMS_REQUEST = 'ITEMS_REQUEST';

Além deste atributo de tipo, a estrutura de um objeto de ação é totalmente de responsabilidade do desenvolvedor. Recomenda-se manter seu objeto de ação o mais leve possível e passar apenas as informações necessárias.

Para causar qualquer mudança na loja, você precisa despachar uma ação primeiro usando a função store.dispatch (). O objeto de ação é o seguinte -

{ type: GET_ORDER_STATUS , payload: {orderId,userId } }
{ type: GET_WISHLIST_ITEMS, payload: userId }

Criadores de ações

Criadores de ação são as funções que encapsulam o processo de criação de um objeto de ação. Essas funções simplesmente retornam um objeto Js simples que é uma ação. Promove a escrita de código limpo e ajuda a alcançar a capacidade de reutilização.

Deixe-nos aprender sobre o criador de ação, que permite que você despache uma ação, ‘ITEMS_REQUEST’que as solicitações dos itens de produtos listam dados do servidor. Enquanto isso, oisLoading O estado é tornado verdadeiro no redutor no tipo de ação 'ITEMS_REQUEST' para indicar que os itens estão sendo carregados e os dados ainda não foram recebidos do servidor.

Inicialmente, o estado isLoading era falso no initialStateobjeto assumindo que nada está carregando. Quando os dados são recebidos no navegador, o estado isLoading será retornado como falso no tipo de ação 'ITEMS_REQUEST_SUCCESS' no redutor correspondente. Este estado pode ser usado como um suporte em componentes de reação para exibir o carregador / mensagem em sua página enquanto a solicitação de dados está ativa. O criador da ação é o seguinte -

const ITEMS_REQUEST = ‘ITEMS_REQUEST’ ;
const ITEMS_REQUEST_SUCCESS = ‘ITEMS_REQUEST_SUCCESS’ ;
export function itemsRequest(bool,startIndex,endIndex) {
   let payload = {
      isLoading: bool,
      startIndex,
      endIndex
   }
   return {
      type: ITEMS_REQUEST,
      payload
   }
}
export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

Para invocar uma função de despacho, você precisa passar a ação como um argumento para a função de despacho.

dispatch(itemsRequest(true,1, 20));
dispatch(itemsRequestSuccess(false));

Você pode despachar uma ação diretamente usando store.dispatch (). No entanto, é mais provável que você o acesse com o método auxiliar react-Redux chamadoconnect(). Você também pode usarbindActionCreators() método para vincular muitos criadores de ação com a função de despacho.

Uma função é um processo que recebe entradas chamadas de argumentos e produz alguma saída conhecida como valor de retorno. Uma função é chamada pura se obedecer às seguintes regras -

  • Uma função retorna o mesmo resultado para os mesmos argumentos.

  • Sua avaliação não tem efeitos colaterais, ou seja, não altera os dados de entrada.

  • Sem mutação de variáveis ​​locais e globais.

  • Não depende do estado externo como uma variável global.

Tomemos o exemplo de uma função que retorna duas vezes o valor passado como uma entrada para a função. Em geral, é escrito como f (x) => x * 2. Se uma função for chamada com um valor de argumento 2, a saída seria 4, f (2) => 4.

Vamos escrever a definição da função em JavaScript conforme mostrado abaixo -

const double = x => x*2; // es6 arrow function
console.log(double(2));  // 4

Here, double is a pure function.

De acordo com os três princípios no Redux, as alterações devem ser feitas por uma função pura, ou seja, redutor no Redux. Agora, surge uma questão de por que um redutor deve ser uma função pura.

Suponha que você deseja despachar uma ação cujo tipo é 'ADD_TO_CART_SUCCESS' para adicionar um item ao seu aplicativo de carrinho de compras clicando no botão adicionar ao carrinho.

Vamos supor que o redutor está adicionando um item ao seu carrinho conforme mostrado abaixo -

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         state.isAddedToCart = !state.isAddedToCart; //original object altered
         return state;
      default:
         return state;
   }
}
export default addToCartReducer ;

Vamos supor, isAddedToCart é uma propriedade no objeto de estado que permite a você decidir quando desabilitar o botão 'adicionar ao carrinho' para o item retornando um valor booleano ‘true or false’. Isso evita que o usuário adicione o mesmo produto várias vezes. Agora, em vez de retornar um novo objeto, estamos alterando a prop isAddedToCart no estado como acima. Agora, se tentarmos adicionar um item ao carrinho, nada acontece. O botão Adicionar ao carrinho não será desativado.

A razão para este comportamento é a seguinte -

Redux compara objetos novos e antigos pela localização de memória de ambos os objetos. Ele espera um novo objeto do redutor se alguma mudança acontecer. E também espera obter o objeto antigo de volta se nenhuma mudança ocorrer. Nesse caso, é o mesmo. Por este motivo, Redux assume que nada aconteceu.

Portanto, é necessário que um redutor seja uma função pura no Redux. A seguir está uma maneira de escrever sem mutação -

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         return {
            ...state,
            isAddedToCart: !state.isAddedToCart
         }
      default:
         return state;
   }
}
export default addToCartReducer;

Redutores são uma função pura no Redux. Funções puras são previsíveis. Redutores são a única maneira de alterar estados no Redux. É o único lugar onde você pode escrever lógica e cálculos. A função redutora aceitará o estado anterior do aplicativo e da ação despachada, calculará o próximo estado e retornará o novo objeto.

As seguintes coisas nunca devem ser realizadas dentro do redutor -

  • Argumentos de mutação de funções
  • Chamadas API e lógica de roteamento
  • Chamando uma função não pura, por exemplo, Math.random ()

A seguir está a sintaxe de um redutor -

(state,action) => newState

Vamos continuar o exemplo de exibição da lista de itens de produtos em uma página da web, discutida no módulo de criadores de ações. Vejamos a seguir como escrever seu redutor.

const initialState = {
   isLoading: false,
   items: []
};
const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      case ‘ITEMS_REQUEST_SUCCESS':
         return Object.assign({}, state, {
            items: state.items.concat(action.items),
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

Em primeiro lugar, se você não definir o estado como 'initialState', Redux chama redutor com o estado indefinido. Neste exemplo de código, a função concat () do JavaScript é usada em 'ITEMS_REQUEST_SUCCESS', o que não altera a matriz existente; em vez disso, retorna uma nova matriz.

Desta forma, você pode evitar a mutação do estado. Nunca escreva diretamente para o estado. Em 'ITEMS_REQUEST', temos que definir o valor do estado da ação recebida.

Já foi discutido que podemos escrever nossa lógica no redutor e podemos dividi-la na base de dados lógicos. Vamos ver como podemos dividir os redutores e combiná-los como redutor de raiz ao lidar com uma aplicação grande.

Suponha que queremos criar uma página da web onde um usuário possa acessar o status do pedido do produto e ver as informações da lista de desejos. Podemos separar a lógica em diferentes arquivos de redutores e fazê-los trabalhar de forma independente. Vamos supor que a ação GET_ORDER_STATUS seja despachada para obter o status do pedido correspondente a algum ID de pedido e ID de usuário.

/reducer/orderStatusReducer.js
import { GET_ORDER_STATUS } from ‘../constants/appConstant’;
export default function (state = {} , action) {
   switch(action.type) {
      case GET_ORDER_STATUS:
         return { ...state, orderStatusData: action.payload.orderStatus };
      default:
         return state;
   }
}

Da mesma forma, suponha que a ação GET_WISHLIST_ITEMS seja despachada para obter as informações da lista de desejos do usuário em relação a um usuário.

/reducer/getWishlistDataReducer.js
import { GET_WISHLIST_ITEMS } from ‘../constants/appConstant’;
export default function (state = {}, action) {
   switch(action.type) {
      case GET_WISHLIST_ITEMS:
         return { ...state, wishlistData: action.payload.wishlistData };
      default:
         return state;
   }
}

Agora, podemos combinar os dois redutores usando o utilitário Redux combineReducers. Os combineReducers geram uma função que retorna um objeto cujos valores são funções redutoras diferentes. Você pode importar todos os redutores no arquivo do redutor de índice e combiná-los como um objeto com seus respectivos nomes.

/reducer/index.js
import { combineReducers } from ‘redux’;
import OrderStatusReducer from ‘./orderStatusReducer’;
import GetWishlistDataReducer from ‘./getWishlistDataReducer’;

const rootReducer = combineReducers ({
   orderStatusReducer: OrderStatusReducer,
   getWishlistDataReducer: GetWishlistDataReducer
});
export default rootReducer;

Agora, você pode passar este rootReducer para o método createStore da seguinte maneira -

const store = createStore(rootReducer);

O próprio Redux é síncrono, então como o async operações como network requesttrabalhar com Redux? Aqui, os middlewares são úteis. Conforme discutido anteriormente, os redutores são o local onde toda a lógica de execução é escrita. O redutor não tem nada a ver com quem o executa, quanto tempo está levando ou registrando o estado do aplicativo antes e depois da ação ser despachada.

Nesse caso, a função de middleware Redux fornece um meio para interagir com a ação despachada antes que cheguem ao redutor. Funções de middleware personalizadas podem ser criadas escrevendo funções de alta ordem (uma função que retorna outra função), que envolve alguma lógica. Vários middlewares podem ser combinados para adicionar novas funcionalidades e cada middleware não requer nenhum conhecimento do que veio antes e depois. Você pode imaginar middlewares em algum lugar entre ação despachada e redutor.

Normalmente, middlewares são usados ​​para lidar com ações assíncronas em seu aplicativo. Redux fornece uma API chamada applyMiddleware que nos permite usar middleware customizado, bem como middlewares Redux como redux-thunk e redux-promessa. Aplica middlewares para armazenar. A sintaxe de uso da API applyMiddleware é -

applyMiddleware(...middleware)

E isso pode ser aplicado à loja da seguinte forma -

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(rootReducer, applyMiddleware(thunk));

Middlewares permitem que você escreva um despachante de ação que retorna uma função ao invés de um objeto de ação. Exemplo para o mesmo é mostrado abaixo -

function getUser() {
   return function() {
      return axios.get('/get_user_details');
   };
}

O envio condicional pode ser escrito dentro do middleware. Cada middleware recebe o despacho da loja para que possam despachar uma nova ação e funções getState como argumentos para que possam acessar o estado atual e retornar uma função. Qualquer valor de retorno de uma função interna estará disponível como o valor da própria função de despacho.

A seguir está a sintaxe de um middleware -

({ getState, dispatch }) => next => action

A função getState é útil para decidir se novos dados devem ser buscados ou se o resultado do cache deve ser retornado, dependendo do estado atual.

Vejamos um exemplo de função de log de middleware personalizado. Ele simplesmente registra a ação e o novo estado.

import { createStore, applyMiddleware } from 'redux'
import userLogin from './reducers'

function logger({ getState }) {
   return next => action => {
      console.log(‘action’, action);
      const returnVal = next(action);
      console.log('state when action is dispatched', getState()); 
      return returnVal;
   }
}

Agora aplique o middleware do logger à loja, escrevendo a seguinte linha de código -

const store = createStore(userLogin , initialState=[ ] , applyMiddleware(logger));

Despache uma ação para verificar a ação despachada e o novo estado usando o código abaixo -

store.dispatch({
   type: 'ITEMS_REQUEST', 
	isLoading: true
})

Outro exemplo de middleware onde você pode controlar quando mostrar ou ocultar o carregador é fornecido abaixo. Este middleware mostra o carregador quando você está solicitando qualquer recurso e o oculta quando a solicitação do recurso é concluída.

import isPromise from 'is-promise';

function loaderHandler({ dispatch }) {
   return next => action => {
      if (isPromise(action)) {
         dispatch({ type: 'SHOW_LOADER' });
         action
            .then(() => dispatch({ type: 'HIDE_LOADER' }))
            .catch(() => dispatch({ type: 'HIDE_LOADER' }));
      }
      return next(action);
   };
}
const store = createStore(
   userLogin , initialState = [ ] , 
   applyMiddleware(loaderHandler)
);

Redux-Devtools nos fornece uma plataforma de depuração para aplicativos Redux. Ele nos permite realizar depuração de viagem no tempo e edição ao vivo. Alguns dos recursos da documentação oficial são os seguintes -

  • Ele permite que você inspecione cada estado e carga útil de ação.

  • Ele permite que você volte no tempo “cancelando” ações.

  • Se você alterar o código do redutor, cada ação “preparada” será reavaliada.

  • Se os redutores dispararem, podemos identificar o erro e também em que ação isso aconteceu.

  • Com persistState () store enhancer, você pode persistir sessões de depuração em recarregamentos de página.

Existem duas variantes de Redux dev-tools conforme fornecido abaixo -

Redux DevTools - Ele pode ser instalado como um pacote e integrado em seu aplicativo conforme fornecido abaixo -

https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md#manual-integration

Redux DevTools Extension - Uma extensão de navegador que implementa as mesmas ferramentas de desenvolvedor para Redux é a seguinte -

https://github.com/zalmoxisus/redux-devtools-extension

Agora vamos verificar como podemos pular ações e voltar no tempo com a ajuda da ferramenta Redux dev. As capturas de tela a seguir explicam as ações que despachamos anteriormente para obter a lista de itens. Aqui podemos ver as ações despachadas na guia do inspetor. À direita, você pode ver a guia Demo que mostra a diferença na árvore de estados.

Você se familiarizará com esta ferramenta quando começar a usá-la. Você pode despachar uma ação sem escrever o código real apenas a partir desta ferramenta de plug-in Redux. Uma opção do Dispatcher na última linha o ajudará com isso. Vamos verificar a última ação em que os itens foram obtidos com sucesso.

Recebemos uma série de objetos como resposta do servidor. Todos os dados estão disponíveis para exibição de listagem em nossa página. Você também pode acompanhar o estado da loja ao mesmo tempo clicando na guia de estado no lado superior direito.

Nas seções anteriores, aprendemos sobre depuração de viagem no tempo. Vamos agora verificar como pular uma ação e voltar no tempo para analisar o estado de nosso aplicativo. Conforme você clica em qualquer tipo de ação, duas opções: 'Pular' e 'Pular' aparecem.

Ao clicar no botão pular em um determinado tipo de ação, você pode pular uma ação específica. Ele age como se a ação nunca tivesse acontecido. Quando você clica no botão de salto em determinado tipo de ação, isso o levará ao estado em que a ação ocorreu e pulará todas as ações restantes na sequência. Desta forma, você será capaz de manter o estado quando uma determinada ação aconteceu. Este recurso é útil para depurar e localizar erros no aplicativo.

Ignoramos a última ação e todos os dados de listagem em segundo plano desapareceram. Isso remonta ao tempo em que os dados dos itens não chegaram e nosso aplicativo não tem dados para renderizar na página. Na verdade, torna a codificação e a depuração mais fáceis.

Testar o código Redux é fácil, pois escrevemos principalmente funções, e a maioria delas são puras. Portanto, podemos testá-lo sem nem mesmo zombar deles. Aqui, estamos usando o JEST como mecanismo de teste. Ele funciona no ambiente do nó e não acessa o DOM.

Podemos instalar o JEST com o código fornecido a seguir -

npm install --save-dev jest

Com o babel, você precisa instalar babel-jest como segue -

npm install --save-dev babel-jest

E configure-o para usar os recursos do babel-preset-env no arquivo .babelrc da seguinte maneira -

{ 
   "presets": ["@babel/preset-env"] 
}
And add the following script in your package.json:
{ 
   //Some other code 
   "scripts": {
      //code
      "test": "jest", 
      "test:watch": "npm test -- --watch" 
   }, 
   //code 
}

Finalmente, run npm test or npm run test. Vamos verificar como podemos escrever casos de teste para criadores e redutores de ação.

Casos de teste para criadores de ação

Vamos supor que você tenha um criador de ação conforme mostrado abaixo -

export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

Este criador de ação pode ser testado conforme abaixo -

import * as action from '../actions/actions';
import * as types from '../../constants/ActionTypes';

describe('actions', () => {
   it('should create an action to check if item is loading', () => { 
      const isLoading = true, 
      const expectedAction = { 
         type: types.ITEMS_REQUEST_SUCCESS, isLoading 
      } 
      expect(actions.itemsRequestSuccess(isLoading)).toEqual(expectedAction) 
   })
})

Casos de teste para redutores

Aprendemos que o redutor deve retornar um novo estado quando a ação é aplicada. Portanto, o redutor é testado neste comportamento.

Considere um redutor conforme abaixo -

const initialState = {
   isLoading: false
};
const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

Para testar o redutor acima, precisamos passar o estado e a ação para o redutor e retornar um novo estado conforme mostrado abaixo -

import reducer from '../../reducer/reducer' 
import * as types from '../../constants/ActionTypes'

describe('reducer initial state', () => {
   it('should return the initial state', () => {
      expect(reducer(undefined, {})).toEqual([
         {
            isLoading: false,
         }
      ])
   })
   it('should handle ITEMS_REQUEST', () => {
      expect(
         reducer(
            {
               isLoading: false,
            },
            {
               type: types.ITEMS_REQUEST,
               payload: { isLoading: true }
            }
         )
      ).toEqual({
         isLoading: true
      })
   })
})

Se você não está familiarizado com a escrita de casos de teste, pode verificar os fundamentos do JEST .

Nos capítulos anteriores, aprendemos o que é Redux e como ele funciona. Vamos agora verificar a integração da parte view com o Redux. Você pode adicionar qualquer camada de visualização ao Redux. Também discutiremos a biblioteca react e o Redux.

Digamos que vários componentes de reação precisem exibir os mesmos dados de maneiras diferentes sem passá-los como um suporte para todos os componentes, do componente de nível superior ao inferior. Seria ideal armazená-lo fora dos componentes de reação. Porque ajuda na recuperação de dados mais rápida, pois você não precisa passar os dados totalmente para os diferentes componentes.

Vamos discutir como isso é possível com Redux. Redux fornece o pacote react-redux para vincular componentes react com dois utilitários, conforme indicado abaixo -

  • Provider
  • Connect

O provedor disponibiliza a loja para o restante do aplicativo. A função de conexão ajuda a reagir o componente a se conectar à loja, respondendo a cada mudança que ocorre no estado da loja.

Vamos dar uma olhada no root index.js arquivo que cria a loja e usa um provedor que habilita a loja para o resto do aplicativo em um aplicativo react-redux.

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers/reducer'
import thunk from 'redux-thunk';
import App from './components/app'
import './index.css';

const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
   applyMiddleware(thunk)
)
render(
   <Provider store = {store}>
      <App />
   </Provider>,
   document.getElementById('root')
)

Sempre que ocorre uma alteração em um aplicativo react-redux, mapStateToProps () é chamado. Nesta função, especificamos exatamente qual estado precisamos fornecer ao nosso componente de reação.

Com a ajuda da função connect () explicada abaixo, estamos conectando o estado desse aplicativo ao componente de reação. Connect () é uma função de alta ordem que leva componente como parâmetro. Ele executa certas operações e retorna um novo componente com dados corretos que finalmente exportamos.

Com a ajuda de mapStateToProps (), fornecemos esses estados de armazenamento como suporte para nosso componente de reação. Este código pode ser empacotado em um componente de contêiner. O motivo é separar questões como busca de dados, preocupação de renderização e capacidade de reutilização.

import { connect } from 'react-redux'
import Listing from '../components/listing/Listing' //react component
import makeApiCall from '../services/services' //component to make api call

const mapStateToProps = (state) => {
   return {
      items: state.items,
      isLoading: state.isLoading
   };
};
const mapDispatchToProps = (dispatch) => {
   return {
      fetchData: () => dispatch(makeApiCall())
   };
};
export default connect(mapStateToProps, mapDispatchToProps)(Listing);

A definição de um componente para fazer uma chamada de api no arquivo services.js é a seguinte -

import axios from 'axios'
import { itemsLoading, itemsFetchDataSuccess } from '../actions/actions'

export default function makeApiCall() {
   return (dispatch) => {
      dispatch(itemsLoading(true));
      axios.get('http://api.tvmaze.com/shows')
      .then((response) => {
         if (response.status !== 200) {
            throw Error(response.statusText);
         }
         dispatch(itemsLoading(false));
         return response;
      })
      .then((response) => dispatch(itemsFetchDataSuccess(response.data)))
   };
}

A função mapDispatchToProps () recebe a função dispatch como um parâmetro e retorna seus adereços de retorno de chamada como um objeto simples que você passa para seu componente react.

Aqui, você pode acessar fetchData como um adereço em seu componente de listagem de reação, que despacha uma ação para fazer uma chamada de API. mapDispatchToProps () é usado para despachar uma ação para armazenar. No react-redux, os componentes não podem acessar a loja diretamente. A única maneira é usar connect ().

Vamos entender como o react-redux funciona através do diagrama abaixo -

STORE - Armazena todo o estado do seu aplicativo como um objeto JavaScript

PROVIDER - Disponibiliza lojas

CONTAINER - Obtenha o estado dos aplicativos e forneça-o como um suporte para os componentes

COMPONENT - O usuário interage por meio do componente de visualização

ACTIONS - Causa uma mudança na loja, pode ou não alterar o estado do seu aplicativo

REDUCER - Única maneira de alterar o estado do aplicativo, aceitar o estado e a ação e retornar o estado atualizado.

No entanto, Redux é uma biblioteca independente e pode ser usada com qualquer camada de IU. React-redux é o Redux oficial, vinculando a interface do usuário com o react. Além disso, incentiva uma boa estrutura do aplicativo Redux reagir. O React-redux implementa internamente a otimização de desempenho, de forma que a re-renderização do componente ocorra apenas quando necessário.

Para resumir, Redux não foi projetado para escrever o código mais curto e rápido. O objetivo é fornecer um contêiner de gerenciamento de estado previsível. Isso nos ajuda a entender quando um determinado estado mudou ou de onde os dados vieram.

Aqui está um pequeno exemplo de aplicação react e Redux. Você também pode tentar desenvolver pequenos aplicativos. O código de amostra para aumentar ou diminuir o contador é fornecido abaixo -

Este é o arquivo raiz que é responsável pela criação de armazenamento e renderização de nosso componente react app.

/src/index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux';
import reducer from '../src/reducer/index'
import App from '../src/App'
import './index.css';

const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && 
   window.__REDUX_DEVTOOLS_EXTENSION__()
)
render(
   <Provider store = {store}>
      <App />
   </Provider>, document.getElementById('root')
)

Este é o nosso componente raiz do react. Ele é responsável por renderizar o componente do contêiner de contador como um filho.

/src/app.js

import React, { Component } from 'react';
import './App.css';
import Counter from '../src/container/appContainer';

class App extends Component {
   render() {
      return (
         <div className = "App">
            <header className = "App-header">
               <Counter/>
            </header>
         </div>
      );
   }
}
export default App;

A seguir está o componente do contêiner que é responsável por fornecer o estado do Redux para o componente de reação -

/container/counterContainer.js

import { connect } from 'react-redux'
import Counter from '../component/counter'
import { increment, decrement, reset } from '../actions';

const mapStateToProps = (state) => {
   return {
      counter: state
   };
};
const mapDispatchToProps = (dispatch) => {
   return {
      increment: () => dispatch(increment()),
      decrement: () => dispatch(decrement()),
      reset: () => dispatch(reset())
   };
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

A seguir está o componente de reação responsável pela parte da visualização -

/component/counter.js
import React, { Component } from 'react';
class Counter extends Component {
   render() {
      const {counter,increment,decrement,reset} = this.props;
      return (
         <div className = "App">
            <div>{counter}</div>
            <div>
               <button onClick = {increment}>INCREMENT BY 1</button>
            </div>
            <div>
               <button onClick = {decrement}>DECREMENT BY 1</button>
            </div>
            <button onClick = {reset}>RESET</button>
         </div>
      );
   }
}
export default Counter;

A seguir estão os criadores de ação responsáveis ​​por criar uma ação -

/actions/index.js
export function increment() {
   return {
      type: 'INCREMENT'
   }
}
export function decrement() {
   return {
      type: 'DECREMENT'
   }
}
export function reset() {
   return { type: 'RESET' }
}

Abaixo, mostramos a linha de código do arquivo redutor que é responsável por atualizar o estado no Redux.

reducer/index.js
const reducer = (state = 0, action) => {
   switch (action.type) {
      case 'INCREMENT': return state + 1
      case 'DECREMENT': return state - 1
      case 'RESET' : return 0 default: return state
   }
}
export default reducer;

Inicialmente, o aplicativo se parece com o seguinte -

Quando eu clico em incrementar duas vezes, a tela de saída será como mostrado abaixo -

Quando o diminuímos uma vez, ele mostra a seguinte tela -

E a redefinição levará o aplicativo de volta ao estado inicial, que é o valor do contador 0. Isso é mostrado abaixo -

Vamos entender o que acontece com as ferramentas de desenvolvimento Redux quando a primeira ação de incremento ocorre -

O estado do aplicativo será movido para o momento em que apenas a ação de incremento é despachada e o restante das ações é ignorado.

Nós encorajamos o desenvolvimento de um pequeno aplicativo Todo como uma atribuição sua e a entender melhor a ferramenta Redux.