Artigo original: How to Use Redux in Your React TypeScript App
O Redux é um contêiner de estado previsível para aplicações escritas em JavaScript. Ele também é popularmente conhecido como uma biblioteca para o gerenciamento de estado em aplicações do React.
Utilizar o Redux junto com TypeScript oferece ao desenvolvedor uma melhor experiência, pois o TypeScript é um superconjunto do JavaScript que verifica o código para torná-lo robusto e compreensível.
Neste guia, mostrarei como usar o Redux em seu projeto com TypeScript criando uma aplicação do React que permite adicionar, excluir e exibir artigos.
Vamos lá!
- Pré-requisitos
- Configurando o projeto
- Criando os tipos
- Criando os tipos de ação
- Criando os criadores de ação
- Criando um redutor
- Criando um repositório
- Criando os componentes
Pré-requisitos
Este tutorial pressupõe que você tenha pelo menos um conhecimento básico de React, Redux e TypeScript.
Portanto, se não estiver familiarizado com essas tecnologias, tente primeiro ler este guia prático do TypeScript ou este tutorial do React Redux (textos em inglês). Caso contrário, vamos começar.
Configurando o projeto
Para usar o Redux e o TypeScript, precisamos criar uma aplicação em React.
Para isso, vamos abrir a CLI (interface de linha de comando) e executar este comando:
npx create-react-app my-app --template typescript
A seguir, vamos estruturar o projeto do seguinte modo:
├── src
| ├── components
| | ├── AddArticle.tsx
| | └── Article.tsx
| ├── store
| | ├── actionCreators.ts
| | ├── actionTypes.ts
| | └── reducer.ts
| ├── type.d.ts
| ├── App.test.tsx
| ├── App.tsx
| ├── index.css
| ├── index.tsx
| ├── react-app-env.d.ts
| └── setupTests.ts
├── tsconfig.json
├── package.json
└── yarn.lock
A estrutura de arquivos do projeto é bastante simples. No entanto, há duas coisas que você deve prestar atenção:
- A pasta
store
do repositório, que contém arquivos relacionados ao React Redux. - O arquivo
type.d.ts
, que contém os tipos do TypeScript, que podem ser usados agora em outros arquivos sem importar.
Dito isso, agora, podemos instalar o Redux e criar nosso primeiro repositório.
Com isso, vamos abrir o projeto e executar o seguinte comando:
yarn add redux react-redux redux-thunk
Se, no lugar do Yarn, você estiver usando o npm
, execute:
npm install redux react-redux redux-thunk
Também precisamos instalar os tipos como dependências de desenvolvimento para ajudar o TypeScript a entender as bibliotecas.
Então, vamos executar este comando novamente na CLI.
yarn add -D @types/redux @types/react-redux @types/redux-thunk
No npm
:
npm install -D @types/redux @types/react-redux @types/redux-thunk
Ótimo! A partir de agora podemos criar os tipos do TypeScript para o projeto na próxima seção.
Criando os tipos
Os tipos do TypeScript permitem definir tipos para suas variáveis, parâmetros de função e assim por diante.
- type.d.ts
interface IArticle {
id: number
title: string
body: string
}
type ArticleState = {
articles: IArticle[]
}
type ArticleAction = {
type: string
article: IArticle
}
type DispatchType = (args: ArticleAction) => ArticleAction
Aqui, começamos declarando a interface IArticle
, que reflete a forma de um determinado artigo.
Em seguida, temos ArticleState
, ArticleAction
e DispatchType
, que servirão como tipos para, respectivamente, o objeto de estado, os criadores de ação e a função de dispatch fornecida pelo Redux.
Dito isso, agora temos os tipos necessários para começar a usar o React Redux. A seguir, vamos criar os tipos de ação.
Criando os tipos de ação
- store/actionTypes.ts
export const ADD_ARTICLE = "ADD_ARTICLE"
export const REMOVE_ARTICLE = "REMOVE_ARTICLE"
Precisamos de dois tipos de ação para o repositório em Redux, um para adicionar artigos e outro para excluí-los.
Criando os criadores de ação
- store/actionCreators.ts
import * as actionTypes from "./actionTypes"
export function addArticle(article: IArticle) {
const action: ArticleAction = {
type: actionTypes.ADD_ARTICLE,
article,
}
return simulateHttpRequest(action)
}
export function removeArticle(article: IArticle) {
const action: ArticleAction = {
type: actionTypes.REMOVE_ARTICLE,
article,
}
return simulateHttpRequest(action)
}
export function simulateHttpRequest(action: ArticleAction) {
return (dispatch: DispatchType) => {
setTimeout(() => {
dispatch(action)
}, 500)
}
}
Neste tutorial, simularei a solicitação HTTP atrasando-a por meio segundo. Se quiser, porém, sinta-se à vontade para usar um servidor real.
Aqui, a função addArticle
fará o dispatching de uma ação para adicionar um novo artigo, enquanto o método removeArticle
fará o oposto. Portanto, exclua o objeto passado como argumento.
Criando um redutor
Um redutor (em inglês, reducer) é uma função pura que recebe o estado do repositório e uma ação como parâmetro e depois retorna o estado atualizado.
- store/reducer.ts
import * as actionTypes from "./actionTypes"
const initialState: ArticleState = {
articles: [
{
id: 1,
title: "post 1",
body:
"Quisque cursus, metus vitae pharetra Nam libero tempore, cum soluta nobis est eligendi",
},
{
id: 2,
title: "post 2",
body:
"Harum quidem rerum facilis est et expedita distinctio quas molestias excepturi sint",
},
],
}
Como você pode ver aqui, declaramos um estado inicial para que alguns artigos sejam exibidos quando a página for carregada. O objeto de estado precisa corresponder ao tipo ArticleState
– caso contrário, o TypeScript gerará um erro.
- store/reducer.ts
const reducer = (
state: ArticleState = initialState,
action: ArticleAction
): ArticleState => {
switch (action.type) {
case actionTypes.ADD_ARTICLE:
const newArticle: IArticle = {
id: Math.random(), // not really unique
title: action.article.title,
body: action.article.body,
}
return {
...state,
articles: state.articles.concat(newArticle),
}
case actionTypes.REMOVE_ARTICLE:
const updatedArticles: IArticle[] = state.articles.filter(
article => article.id !== action.article.id
)
return {
...state,
articles: updatedArticles,
}
}
return state
}
export default reducer
Em seguida, temos a função reducer
, que espera o estado anterior e uma ação para poder atualizar o repositório. Aqui, temos duas ações: uma para adicionar e outra para excluir.
Com isso definido, agora podemos manipular o estado com o redutor. Próximo passo: criar um repositório para o projeto.
Criando um repositório
O repositório em Redux é, na prática, onde vive o estado da sua aplicação.
- index.tsx
import * as React from "react"
import { render } from "react-dom"
import { createStore, applyMiddleware, Store } from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"
import App from "./App"
import reducer from "./store/reducer"
const store: Store<ArticleState, ArticleAction> & {
dispatch: DispatchType
} = createStore(reducer, applyMiddleware(thunk))
const rootElement = document.getElementById("root")
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
Como você pode ver, importamos a função do redutor e a passamos como argumento para o método createStore
para criar um repositório do Redux. O middleware redux-thunk
precisa ser passado como um segundo parâmetro, bem como o método para poder manipular o código assíncrono.
Em seguida, conectamos o React ao Redux fornecendo o objeto store
como props ao componente Provider
.
Agora, podemos usar o Redux neste projeto e acessar o repositório. Então, vamos criar os componentes para obter e manipular os dados.
Criando os componentes
- components/AddArticle.tsx
import * as React from "react"
type Props = {
saveArticle: (article: IArticle | any) => void
}
export const AddArticle: React.FC<Props> = ({ saveArticle }) => {
const [article, setArticle] = React.useState<IArticle | {}>()
const handleArticleData = (e: React.FormEvent<HTMLInputElement>) => {
setArticle({
...article,
[e.currentTarget.id]: e.currentTarget.value,
})
}
const addNewArticle = (e: React.FormEvent) => {
e.preventDefault()
saveArticle(article)
}
return (
<form onSubmit={addNewArticle} className="Add-article">
<input
type="text"
id="title"
placeholder="Title"
onChange={handleArticleData}
/>
<input
type="text"
id="body"
placeholder="Description"
onChange={handleArticleData}
/>
<button disabled={article === undefined ? true : false}>
Add article
</button>
</form>
)
}
Para adicionar um novo artigo, usaremos este componente de formulário. Ele recebe como parâmetro a função saveArticle
, que permite adicionar um novo artigo ao repositório.
O objeto do artigo deve seguir o tipo IArticle
para satisfazer os critérios TypeScript.
- components/Article.tsx
import * as React from "react"
import { Dispatch } from "redux"
import { useDispatch } from "react-redux"
type Props = {
article: IArticle
removeArticle: (article: IArticle) => void
}
export const Article: React.FC<Props> = ({ article, removeArticle }) => {
const dispatch: Dispatch<any> = useDispatch()
const deleteArticle = React.useCallback(
(article: IArticle) => dispatch(removeArticle(article)),
[dispatch, removeArticle]
)
return (
<div className="Article">
<div>
<h1>{article.title}</h1>
<p>{article.body}</p>
</div>
<button onClick={() => deleteArticle(article)}>Delete</button>
</div>
)
}
O componente Article
mostra um objeto de artigo.
A função removeArticle
deve fazer o dispatching para acessar o repositório e, em seguida, excluir um determinado artigo. Essa é a razão pela qual usamos o hook useDispatch
aqui, que permite que o Redux complete a ação de remoção.
Por conseguinte, o uso de useCallback
ajuda a evitar renderizações desnecessárias ao memorizar valores como dependências.
Finalmente, temos os componentes que precisamos para adicionar e mostrar os artigos. Agora, vamos adicionar a última peça ao quebra-cabeça, usando tudo isso no arquivo App.tsx
.
- App.tsx
import * as React from "react"
import { useSelector, shallowEqual, useDispatch } from "react-redux"
import "./styles.css"
import { Article } from "./components/Article"
import { AddArticle } from "./components/AddArticle"
import { addArticle, removeArticle } from "./store/actionCreators"
import { Dispatch } from "redux"
const App: React.FC = () => {
const articles: readonly IArticle[] = useSelector(
(state: ArticleState) => state.articles,
shallowEqual
)
const dispatch: Dispatch<any> = useDispatch()
const saveArticle = React.useCallback(
(article: IArticle) => dispatch(addArticle(article)),
[dispatch]
)
return (
<main>
<h1>My Articles</h1>
<AddArticle saveArticle={saveArticle} />
{articles.map((article: IArticle) => (
<Article
key={article.id}
article={article}
removeArticle={removeArticle}
/>
))}
</main>
)
}
export default App
O hook useSelector
permite o acesso ao estado do repositório. Aqui, passamos o shallowEqual
como um segundo argumento para o método, de modo a dizer ao Redux para usar a igualdade rasa (shallow equality, em inglês) ao verificar as alterações.
Em seguida, contamos com useDispatch
para fazer o dispatching de uma ação com o propósito de adicionar artigos no repositório. Por fim, percorremos o array de artigos e passamos cada um deles para o componente Article
para que seja mostrado.
Com isso, podemos navegar até a raiz do projeto e executar este comando:
yarn start
No npm
:
npm start
Se você abrir http://localhost:3000/
no navegador, deverá ver isto:
Ótimo! Nossa aplicação está funcionando. Com isso, terminamos de mostrar o uso do Redux em uma aplicação do React com TypeScript.
Você pode encontrar o projeto completo nesta CodeSandbox.
Você pode encontrar outros conteúdos excelentes como este no blog do autor ou segui-lo no Twitter para ser notificado sobre novos textos produzidos por ele.
Obrigado pela leitura.