Como funcionam os redutores de redux
Publicados: 2022-03-10state
, com certeza você já se deparou com os redutores. Este tutorial explicará o conceito de redutores e como eles funcionam especificamente no Redux.Neste tutorial, vamos aprender o conceito de redutores e como eles funcionam, especificamente em aplicativos React. Para entender e usar melhor o Redux, é essencial um entendimento sólido dos redutores. Os redutores fornecem uma maneira de atualizar o estado de um aplicativo usando uma ação. É parte integrante da biblioteca Redux.
Este tutorial é para desenvolvedores que desejam aprender mais sobre Redux Reducers. Uma compreensão de React e Redux seria benéfica. No final do tutorial, você deve ter uma melhor compreensão do papel que os Redutores desempenham no Redux. Estaremos escrevendo demonstrações de código e um aplicativo para entender melhor os Redutores e como isso afeta o estado de um aplicativo.
O que é um redutor
Um Reducer é uma função pura que recebe o estado de um aplicativo e ação como argumentos e retorna um novo estado. Por exemplo, um redutor de autenticação pode obter um estado inicial de um aplicativo na forma de um objeto vazio e uma ação que informa que um usuário efetuou login e retornou um novo estado de aplicativo com um usuário conectado.
Funções puras são funções que não têm nenhum efeito colateral e retornarão os mesmos resultados se os mesmos argumentos forem passados.
Abaixo está um exemplo de uma função pura:
const add = (x, y) => x + y; add(2, 5);
O exemplo acima retorna um valor baseado nas entradas, se você passar 2
e 5
, sempre obterá 7
, desde que seja a mesma entrada, nada mais afete a saída que você obtém, esse é um exemplo de uma função pura.
Abaixo está um exemplo de uma função redutora que recebe um estado e uma ação.
const initialState = {}; const cartReducer = (state = initialState, action) => { // Do something here }
Vamos definir os dois parâmetros que um redutor recebe, state
e action
.
Estado
Um estado são os dados com os quais seu(s) componente(s) está(ão) trabalhando — ele contém os dados que um componente requer e determina o que um componente renderiza. Uma vez que um objeto de state
é alterado, o componente é renderizado novamente. Se um estado de aplicativo é gerenciado pelo Redux, o redutor é onde as mudanças de estado acontecem.
Açao
Uma ação é um objeto que contém a carga útil de informações. Eles são a única fonte de informação para que a loja Redux seja atualizada. Os redutores atualizam o armazenamento com base no valor do action.type
. Aqui vamos definir o action.type
como ADD_TO_CART
.
De acordo com a documentação oficial do Redux, as ações são as únicas coisas que acionam alterações em um aplicativo Redux, elas contêm a carga útil para alterações em uma loja de aplicativos. Actions são objetos JavaScript que informam ao Redux o tipo de ação a ser executada, geralmente são definidas como funções como a abaixo:
const action = { type: 'ADD_TO_CART', payload: { product: 'margarine', quantity: 4 } }
O código acima é um valor típico de payload
que contém o que um usuário está enviando e será usado para atualizar o estado do aplicativo. Como você pode ver acima, o objeto de ação contém o tipo de ação e um objeto de carga útil que seria necessário para que essa ação específica fosse executada.
Atualizando o estado usando redutores
Para mostrar como os redutores funcionam, vejamos o contador de números abaixo:
const increaseAction = { type: 'INCREASE', }; const decreaseAction = { type: 'DECREASE' }; const countReducer = (state = 0, action) => { switch(action.type){ case INCREASE: return state + 1; case DECREASE : return state -1; default: return state; } };
No código acima, increaseAction
e decreaseAction
são ações usadas no redutor para determinar para qual estado o state
é atualizado. Em seguida, temos uma função redutora chamada countReducer
, que recebe uma action
e um state
inicial cujo valor é 0
. Se o valor de action.type
for INCREASE
, retornamos um novo estado que é incrementado em 1, senão se for DECREASE
um novo estado que é decrementado em 1 é retornado. Nos casos em que nenhuma dessas condições se aplica, retornamos state
.
Atualizando o estado usando redutores: o operador de spread
O estado não pode ser alterado diretamente, para criar ou atualizar o estado, podemos usar o operador de propagação do JavaScript para garantir que não alteramos o valor do estado diretamente, mas sim retornar um novo objeto que contém um estado passado para ele e a carga do usuário.
const contactAction = { type: 'GET_CONTACT', payload: ['0801234567', '0901234567'] }; const initialState = { contacts: [], contact: {}, }; export default function (state = initialState, action) { switch (action.type) { case GET_CONTACTS: return { ...state, contacts: action.payload, }; default: return state; }
No código acima, estamos usando um operador spread para garantir que não alteramos o valor do estado diretamente, desta forma podemos retornar um novo objeto que é preenchido com o estado que é passado para ele e a carga que é enviada pelo do utilizador. Ao usar um operador de spread, podemos garantir que o estado permaneça o mesmo à medida que adicionamos todos os novos itens a ele e também substituimos o campo de contatos no estado se ele estava presente antes.
Redutores Redux em ação — uma demonstração
Para entender melhor os Redux Reducers e como eles funcionam, estaremos implementando um aplicativo simples de localização de detalhes do filme, o código e a versão de trabalho podem ser encontrados aqui no Codesandbox. Para começar, vá ao seu terminal e inicialize um aplicativo react usando o comando abaixo:
create-react-app movie-detail-finder
Uma vez que nosso projeto foi inicializado, vamos instalar os pacotes que precisamos para nosso aplicativo.
npm i axios reactstrap react-redux redux redux-thunk
Uma vez instalados os pacotes, vamos iniciar nosso servidor de desenvolvimento usando o comando:
npm start
O comando acima deve iniciar nosso servidor de desenvolvimento de projeto em nosso navegador. Em seguida, vamos abrir nosso projeto em nosso editor de texto de escolha, dentro da pasta src
do nosso projeto, exclua os seguintes arquivos: App.css
, App.test.js
, serviceWorker.js
e setupTests.js
. Em seguida, vamos excluir todo o código que faz referência aos arquivos excluídos em nosso App.js
.
Para este projeto, usaremos a API Open Movie Database para obter informações, conteúdo e imagens do nosso filme para nosso aplicativo, aqui está um link para a API, você precisa se registrar e obter chaves de acesso para usá-lo para isso application, Assim que terminar, vamos prosseguir com nosso aplicativo criando componentes.
Como criar componentes de aplicativos
Primeiro, dentro da nossa pasta src
no diretório do nosso projeto, crie uma pasta chamada components e dentro da pasta vamos criar duas pastas chamadas Movie
e Searchbar
, nosso componente deve ficar parecido com a imagem abaixo:
Construindo Componente de Filme
Vamos construir o componente Movies
, que descreverá a estrutura dos detalhes do filme que obteremos de nossa API. Para fazer isso, dentro da pasta Movies
do nosso componente, crie um novo arquivo Movie.js
, em seguida crie um componente baseado em classe para os resultados da API, vamos fazer isso abaixo.
import React, { Component } from 'react'; import { Card, CardImg, CardText, CardBody, ListGroup, ListGroupItem, Badge } from 'reactstrap'; import styles from './Movie.module.css'; class Movie extends Component{ render(){ if(this.props.movie){ return ( <div className={styles.Movie}> <h3 className="text-center my-4"> Movie Name: {this.props.movie.Title} </h3> <Card className="text-primary bg-dark"> <CardImg className={styles.Img} top src={this.props.movie.Poster} alt={this.props.movie.Title}/> <CardBody> <ListGroup className="bg-dark"> <ListGroupItem> <Badge color="primary">Actors:</Badge> {this.props.movie.Actors} </ListGroupItem> <ListGroupItem> <Badge color="primary">Genre:</Badge> {this.props.movie.Genre} </ListGroupItem> <ListGroupItem> <Badge color="primary">Year:</Badge> {this.props.movie.Year} </ListGroupItem> <ListGroupItem> <Badge color="primary">Writer(s):</Badge> {this.props.movie.Writer} </ListGroupItem> <ListGroupItem> <Badge color="primary">IMDB Rating:</Badge> {this.props.movie.imdbRating}/10 </ListGroupItem> </ListGroup> <CardText className="mt-3 text-white"> <Badge color="secondary">Plot:</Badge> {this.props.movie.Plot} </CardText> </CardBody> </Card> </div> ) } return null } } export default Movie;
No código acima, Usando componentes do pacote reactstrap
, você pode conferir a documentação aqui. Criamos um componente de cartão que inclui o nome do filme, imagem, gênero, ator, ano, escritor do filme, classificação e enredo. Para facilitar a passagem de dados desse componente, criamos dados para servir de suporte a outros componentes. Em seguida, vamos construir nosso componente Searchbar
.
Criando nosso componente de barra de pesquisa
Nosso componente Searchbar
apresentará uma barra de pesquisa e um componente de botão para pesquisar componentes do filme, vamos fazer isso abaixo:
import React from 'react'; import styles from './Searchbar.module.css'; import { connect } from 'react-redux'; import { fetchMovie } from '../../actions'; import Movie from '../Movie/Movie'; class Searchbar extends React.Component{ render(){ return( <div className={styles.Form}> <div> <form onSubmit={this.formHandler}> <input type="text" placeholder="Movie Title" onChange={e => this.setState({title: e.target.value})} value={this.state.title}/> <button type="submit">Search</button> </form> </div> <Movie movie={this.props.movie}/> </div> ) } }
No código acima, estamos importando connect
do react-redux
que é usado para conectar um componente React ao repositório Redux, fornece ao componente informações do repositório e também fornece funções usadas para despachar ações para o repositório. Em seguida, importamos o componente Movie
e uma função fetchMovie
de actions.
Em seguida, temos uma tag de formulário com uma caixa de entrada para inserir nossos títulos de filmes, usando o gancho setState
do React, adicionamos um evento e valor onChange
que definirá o estado do title
para o valor inserido na caixa de entrada. Temos uma tag de button
para pesquisar títulos de filmes e usando o componente Movie
que importamos, passamos as propriedades do componente como props
para o resultado da pesquisa.
O próximo passo para nós é escrever uma função para enviar nosso título de filme para a API para enviar resultados para nós, também precisamos definir o estado inicial do aplicativo. vamos fazer isso abaixo.
class Searchbar extends React.Component{ state = { title: '' } formHandler = (event) => { event.preventDefault(); this.props.fetchMovie(this.state.title); this.setState({title: ''}); }
Aqui, definimos o estado inicial do aplicativo para strings vazias, criamos uma função formHandler
que recebe um parâmetro de evento e passa a função fetchMovie
da ação e definindo o título como o novo estado do aplicativo. Para completar nossa aplicação, vamos exportar este componente usando a propriedade connect de react-redux
, para isso usaríamos a propriedade react redux mapToStateProps
para selecionar a parte dos dados que nosso componente precisaria, você pode aprender mais sobre mapToStateProps
aqui.
const mapStateToProps = (state) => { return { movie: state.movie } } export default connect(mapStateToProps, { fetchMovie })(Searchbar)
Vamos adicionar estilos ao nosso formulário criando um arquivo Searchbar.module.css
e adicionando os estilos abaixo:
.Form{ margin: 3rem auto; width: 80%; height: 100%; } input{ display: block; height: 45px; border: none; width: 100%; border-radius: 0.5rem; outline: none; padding: 0 1rem; } input:focus, select:focus{ border: 2px rgb(16, 204, 179) solid; } .Form button{ display: block; background: rgb(16, 204, 179); padding: 0.7rem; border-radius: 0.5rem; width: 20%; margin-top: 0.7rem; color: #FFF; border: none; text-decoration: none; transition: all 0.5s; } button:hover{ opacity: 0.6; } @media(max-width: 700px){ input{ height: 40px; padding: 0 1rem; } .Form button{ width: 40%; padding: 0.6rem; } }
Depois de fazer o acima, nosso componente da barra de pesquisa deve ser semelhante à imagem abaixo:
Criando ações para aplicativo
Neste componente, estaremos configurando ações do Redux para nossa aplicação, Primeiro, dentro do diretório src
, crie uma pasta chamada actions
e dentro da pasta, criaremos um arquivo index.js
. Aqui, criaríamos uma função fetchMovie
que recebe um parâmetro de título e busca o filme da API usando o Axios. Vamos fazer isso abaixo:
import axios from 'axios'; export const fetchMovie = (title) => async (dispatch) => { const response = await axios.get( `https://cors-anywhere.herokuapp.com/https://www.omdbapi.com/?t=${title}&apikey=APIKEY`); dispatch({ type: 'FETCH_MOVIE', payload: response.data }) }
No código acima, importamos axios
e criamos uma função chamada fetchMovie
que recebe um parâmetro title
usando async/await para que possamos fazer uma solicitação ao servidor da API. Temos uma função dispatch
que despacha para o Redux o objeto action que é passado para ele. Pelo que temos acima, estamos despachando uma ação com o tipo FETCH_MOVIE
e o payload que contém a resposta que recebemos da API.
NOTA: O apikey
na solicitação será substituído pelo seu próprio apikey
após o registro no OmdbAPI .
Criando redutores de aplicativos
Nesta seção, vamos criar redutores para nossa aplicação.
const fetchMovieReducer = (state = null, action) => { switch(action.type){ case 'FETCH_MOVIE': return action.payload; default: return state; } } const rootReducer = (state, action) => { return { movie: fetchMovieReducer(state, action) } } export default rootReducer;
No código acima, criamos um fetchMovieReducer
que recebe um estado padrão de null
e um parâmetro de action
, usando um operador switch, para o caso FETCH_MOVIE
retornaremos o valor do action.payload
que é o filme que obtivemos da API. Se a ação que tentamos executar não estiver no redutor, retornaremos nosso estado padrão.
Em seguida, criamos uma função rootReducer
que aceitará o estado atual e uma ação como entrada e retornará o fetchMovieReducer
.
Juntar as peças
Nesta seção, terminaríamos nosso aplicativo criando nossa loja redux no index.js
, vamos fazer isso abaixo:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import App from './App'; import 'bootstrap/dist/css/bootstrap.min.css'; import './index.css'; import reducers from './reducers'; const store = createStore(reducers, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <> <App/> </> </Provider>, document.getElementById('root') )
No código acima, criamos a store
de aplicativos usando o método createStore
passando o redutor que criamos e um middleware. Middlewares são addons que nos permitem aprimorar as funcionalidades do Redux. Aqui estamos fazendo uso do middleware Redux Thunk usando applyMiddleware
. O middleware Redux Thunk é necessário para que nossa loja faça atualizações assíncronas. Isso é necessário porque, por padrão, o Redux atualiza a loja de forma síncrona.
Para garantir que nosso aplicativo saiba a loja exata a ser usada, envolvemos nossa aplicação em um componente Provider
e passamos a loja como um prop, fazendo isso, outros componentes em nosso aplicativo podem se conectar e compartilhar informações com a loja.
Vamos adicionar um pouco de estilo ao nosso arquivo index.css
.
*{ margin: 0; padding: 0; box-sizing: border-box; } body{ background: rgb(15, 10, 34); color: #FFF; height: 100vh; max-width: 100%; }
Renderizando e testando um localizador de detalhes de filme
Nesta seção, vamos concluir nosso aplicativo renderizando nosso aplicativo em nosso App.js
, para fazer isso, vamos criar um componente baseado em classe chamado App
e inicializar nossa barra de pesquisa e campo de entrada.
import React from 'react'; import Searchbar from './components/Searchbar/Searchbar'; import styles from './App.module.css'; class App extends React.Component{ render(){ return( <div className={styles.App}> <h1 className={styles.Title}>Movies Search App</h1> <Searchbar/> </div> ) } } export default App;
Aqui, criamos um componente baseado em classe App com um h1
que diz Movie Search App e adicionamos nosso componente Searchbar
. Nossa aplicação deve ficar como a imagem abaixo:
Uma demonstração funcional está disponível no Codesandbox.
Conclusão
Os redutores são uma parte importante do gerenciamento de estado do Redux, com os redutores podemos escrever funções puras para atualizar áreas específicas de nossos aplicativos Redux sem efeitos colaterais. Aprendemos o básico dos redutores do Redux, seus usos e o conceito central de redutores, estado e argumentos.
Você pode levar isso adiante vendo a documentação sobre os redutores Redux aqui. Você pode levar isso adiante e construir mais em redutores Redux, deixe-me saber o que você constrói.
Recursos
- Documentação do React-Redux
- Documentação do Redux
- função
connect()
- função
applyMiddleware