Uma introdução à API de contexto do React
Publicados: 2022-03-10Para este tutorial, você deve ter uma boa compreensão dos ganchos. Ainda assim, antes de começarmos, discutirei brevemente o que são e os ganchos que usaremos neste artigo.
De acordo com os documentos do React:
“ Hooks são uma nova adição no React 16.8. Eles permitem que você use o estado e outros recursos do React sem escrever uma classe.”
Isso é basicamente o que é um gancho React. Ele nos permite usar state, refs e outros recursos do React em nossos componentes funcionais.
Vamos discutir os dois ganchos que encontraremos neste artigo.
O gancho useState
O gancho useState nos permite usar o estado em nossos componentes funcionais. Um gancho useState
recebe o valor inicial de nosso estado como o único argumento e retorna uma matriz de dois elementos. O primeiro elemento é nossa variável de estado e o segundo elemento é uma função na qual podemos usar a atualização do valor da variável de estado.
Vamos dar uma olhada no seguinte exemplo:
import React, {useState} from "react"; function SampleComponent(){ const [count, setCount] = useState(0); }
Aqui, count
é nossa variável de estado e seu valor inicial é 0
enquanto setCount
é uma função que podemos usar para atualizar o valor de count.
O gancho useContext
Discutirei isso mais adiante no artigo, mas esse gancho basicamente nos permite consumir o valor de um contexto. O que isso realmente significa ficará mais claro mais adiante no artigo.
Espaços de trabalho de fios
Os espaços de trabalho do Yarn permitem que você organize a base de código do seu projeto usando um repositório monolítico (monorepo). O React é um bom exemplo de projeto de código aberto que é monorepo e usa espaços de trabalho do Yarn para atingir esse objetivo. Leia um artigo relacionado →
Por que precisamos da API de contexto?
Queremos construir um componente “theme toggler” que alterna entre o modo claro e o modo escuro para nosso aplicativo React. Cada componente precisa ter acesso ao modo de tema atual para que possa ser estilizado de acordo.
Normalmente, forneceríamos o modo de tema atual para todos os componentes por meio de props e atualizaríamos o tema atual usando state
:
import React from "react"; import ReactDOM from "react-dom"; function App() { return ( <div> <Text theme= "blue" /> <h1>{theme}</h1> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
No exemplo de código acima, criamos um componente de texto que renderiza um elemento h1
. A cor do elemento h1
depende do modo de tema atual. Atualmente, o tema é azul. Podemos alternar entre os temas blue
e red
usando state
.
Vamos criar um estado chamado “tema” usando o gancho useState
. O gancho useState
retornará o valor atual do tema e uma função que podemos usar para atualizar o tema.
Então, vamos criar nosso estado de tema:
const [theme, setTheme] = React.useState("blue");
Também adicionaremos um elemento de botão ao nosso componente App
. Este botão será usado para alternar os temas e precisa de um manipulador de eventos de clique. Então, vamos escrever o manipulador de eventos click assim:
const onClickHandler = () => { setTheme(); }
Agora, queremos definir o novo tema para Red
se o tema atual for Blue
e vice-versa. Em vez de usar uma instrução if
, uma maneira mais conveniente de fazer isso é com a ajuda do operador ternário em JavaScript.
setTheme( theme === "red"? "blue": "red");
Então, agora, escrevemos nosso manipulador onClick
. Vamos adicionar este elemento de botão ao componente App
:
<button onClick = {onClickHandler}>Change theme</button>
Vamos também alterar o valor dos adereços do tema do componente Texto para o estado do tema.
<Text theme={theme}/>
Agora, devemos ter isso:
import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; function App() { const[theme, setTheme] = React.useState("red"); const onClickHandler = () => { setTheme( theme === "red"? "blue": "red"); } return ( <div> <Text theme={theme}/> <button onClick = {onClickHandler}>Change theme</button> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
Agora podemos alternar entre nossos dois temas. No entanto, se este fosse um aplicativo muito maior, seria difícil usar o tema em componentes profundamente aninhados e o código se tornaria pesado.
Apresentando a API de contexto
Deixe-me apresentar a API de contexto. De acordo com a documentação do React:
“O contexto fornece uma maneira de passar dados pela árvore de componentes sem ter que passar adereços manualmente em todos os níveis.”
Para uma definição mais detalhada, ele fornece uma maneira de disponibilizar dados específicos para todos os componentes em toda a árvore de componentes, não importa o quão profundamente aninhado esse componente possa estar.
Vejamos este exemplo:
const App = () => { return( <ParentComponent theme = "light"/> ); } const ParentComponent = (props) => ( <Child theme = {props.theme} /> ) const Child = (props) => ( <Grandchild theme = {props.theme} /> ) const Grandchild = (props) => ( <p>Theme: {props.theme}</p> )
No exemplo acima, especificamos o tema do aplicativo usando um props no ParentComponent
chamado theme
. Tivemos que passar esses adereços para todos os componentes na árvore de componentes para chegar onde é necessário, que é o componente GrandChild
. O ChildComponent
não tinha nada a ver com os adereços do tema, mas era usado apenas como intermediário.
Agora, imagine que o componente GrandChild
foi aninhado mais profundamente do que no exemplo principal. Teríamos que passar os adereços do tema da mesma forma que fizemos aqui, o que seria trabalhoso. Este é o problema que o Context
resolve. Com Context
, cada componente na árvore de componentes tem acesso a qualquer dado que decidamos colocar em nosso contexto.
Vamos começar com o Context
É hora de replicar o botão de alternância de tema que criamos no início do artigo com a API de contexto. Desta vez, nosso seletor de temas será um componente separado. Construiremos um componente ThemeToggler
que alterna o tema do nosso aplicativo React usando Context
.
Primeiro, vamos inicializar nosso aplicativo React. (Eu prefiro usar create-react-app
mas você pode usar o método que preferir.)
Depois de inicializar seu projeto React, crie um arquivo chamado ThemeContext.js em sua pasta /src
. Você também pode criar uma pasta chamada /context
e colocar seu arquivo ThemeContext nela, se desejar.
Agora, vamos seguir em frente.
Criando sua API de contexto
Vamos criar nosso contexto de tema em nosso arquivo ThemeContext.js .
Para criar um contexto, usamos React.createContext
que cria um objeto de contexto. Você pode passar qualquer coisa como argumento para React.createContext
. Neste caso, vamos passar uma string que é o modo de tema atual. Então agora nosso modo de tema atual é o modo de tema “leve”.
import React from "react"; const ThemeContext = React.createContext("light"); export default ThemeContext;
Para disponibilizar esse contexto para todos os nossos componentes React, temos que usar um Provider. O que é um Provedor? De acordo com a documentação do React, cada objeto de contexto vem com um componente Provider React que permite que os componentes de consumo assinem as alterações de contexto. É o provedor que permite que o contexto seja consumido por outros componentes. Dito isso, vamos criar nosso provedor.
Acesse seu arquivo App.js. Para criar nosso provedor, temos que importar nosso ThemeContext
.
Uma vez que o ThemeContext
foi importado, temos que incluir o conteúdo do nosso componente App
nas tags ThemeContext.Provider
e dar ao componente ThemeContext.Provider
um props chamado value
que conterá os dados que queremos disponibilizar para nossa árvore de componentes.
function App() { const theme = "light"; return ( <ThemeContext.Provider value = {theme}> <div> </div> </ThemeContext.Provider> ); }
Então agora o valor de “light” está disponível para todos os nossos componentes (que escreveremos em breve).
Criando nosso arquivo de tema
Agora, vamos criar nosso arquivo de tema que conterá os diferentes valores de cores para nossos temas claros e escuros. Crie um arquivo em sua pasta /src
chamado Colors.js .
Em Colors.js , criaremos um objeto chamado AppTheme
. Este objeto conterá as cores dos nossos temas. Quando terminar, exporte o objeto AppTheme
assim:
const AppTheme = { light: { textColor: "#000", backgroundColor: "#fff" }, dark: { textColor: "#fff", backgroundColor: "#333" } } export default AppTheme;
Agora é hora de começar a criar nossos diferentes componentes React.
Criando nossos componentes React
Vamos criar os seguintes componentes:
-
Header
-
ThemeToggler
-
MainWithClass
Header.jsx
import React from "react"; import ThemeToggler from "./ThemeToggler"; const headerStyles = { padding: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center" } const Header = () => { return( <header style = {headerStyles}> <h1>Context API</h1> <ThemeToggler /> </header> ); } export default Header;
ThemeToggler.jsx
(Por enquanto, vamos apenas retornar uma div
vazia.)
import React from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { return( <div style = {themeTogglerStyle}> </div> ); } export default ThemeToggler;
Consumindo Contexto com Componentes Baseados em Classe
Aqui, usaremos o valor do nosso ThemeContext
. Como você já deve saber, temos dois métodos de escrever componentes em React : através de funções ou classes. O processo de contexto de uso em ambos os métodos é diferente então vamos criar dois componentes para servir como a seção principal de nossa aplicação: MainWithClass
e MainWithFunction
.
Vamos começar com MainWithClass
.
MainWithClass.jsx
Teremos que importar nosso ThemeContext
e AppTheme
. Feito isso, escreveremos uma classe que retorne nosso JSX de um método de renderização. Agora temos que consumir nosso contexto. Existem dois métodos para fazer isso com componentes baseados em classe:
- O primeiro método é por meio de
Class.contextType
.
Para usar esse método, atribuímos o objeto context de nossoThemeContext
à propriedadecontextType
de nossa classe. Depois disso, poderemos acessar o valor de contexto usandothis.context
. Você também pode fazer referência a isso em qualquer um dos métodos de ciclo de vida e até mesmo no método de renderização.import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context]; return( <main></main> ); } }
Depois de atribuirThemeContext
à propriedadecontextType
de nossa classe, salvei o objeto de tema atual na variávelcurrentTheme
.
Agora, vamos pegar as cores da variávelcurrentTheme
e usá-las para estilizar algumas marcações.render() { const currentTheme = AppTheme[this.context]; return ( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main>
É isso! Esse método, no entanto, limita você a consumir apenas um contexto. - O segundo método é
ThemeContext.Consumer
que envolve o uso de um Consumer. Cada objeto de contexto também vem com um componente Consumer React que pode ser usado em um componente baseado em classe. O componente consumidor recebe um filho como uma função e essa função retorna um nó React. O valor de contexto atual é passado para essa função como um argumento.
Agora, vamos substituir o código em nosso componenteMainWithClass
por este:class Main extends Component { constructor() { super(); this.state = { } } render(){ return( <ThemeContext.Consumer> { (theme) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } }
Como você pode ver, usamos o valor atual de nossoThemeContext
que chamamos de “tema” e pegamos os valores de cor para esse modo de tema e o atribuímos à variávelcurrentTheme
. Com esse método, você pode usar vários Consumidores.
Esses são os dois métodos de consumo de contexto com componentes baseados em classe.
Consumindo Contexto com Componentes Funcionais
Consumir contexto com componentes funcionais é mais fácil e menos tedioso do que com componentes baseados em classes. Para consumir contexto em um componente funcional, usaremos um gancho chamado useContext
.
Veja como seria consumir nosso ThemeContext
com um componente funcional:
const Main = () => { const theme = useContext(ThemeContext); const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;
Como você pode ver, tudo o que tivemos que fazer foi usar nosso gancho useContext
com nosso ThemeContext
passado como um argumento.
Observação : você precisa usar esses diferentes componentes no arquivo App.js para ver os resultados.
Atualizando nosso tema com o componente ThemeToggler
Agora vamos trabalhar em nosso componente ThemeToggler
. Precisamos ser capazes de alternar entre os temas claros e escuros. Para fazer isso, precisaremos editar nosso ThemeContext.js . Nosso React.createContext
agora receberá um objeto semelhante ao resultado de um gancho useState
como argumento.
const ThemeContext = React.createContext(["light", () => {}]);
Passamos um array para a função React.createContext
. O primeiro elemento no array é o modo de tema atual e o segundo elemento é a função que seria usada para atualizar o tema. Como eu disse, isso apenas se assemelha ao resultado de um gancho useState
, mas não é exatamente o resultado de um gancho useState
.
Agora vamos editar nosso arquivo App.js. Precisamos alterar o valor passado para o provedor para um gancho useState
. Agora o valor do nosso Theme Context é um hook useState
cujo valor padrão é “light”.
function App() { const themeHook = useState("light"); return ( <ThemeContext.Provider value = {themeHook}> <div> <Header /> <Main /> </div> </ThemeContext.Provider> ); }
Escrevendo nosso componente ThemeToggler
Vamos agora realmente escrever nosso componente ThemeToggler
:
import React,{useContext} from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { const[themeMode, setThemeMode] = useContext(ThemeContext); return( <div style = {themeTogglerStyle} onClick = {() => {setThemeMode(themeMode === "light"? "dark": "light")}}> <span title = "switch theme"> {themeMode === "light" ? "" : "️"} </span> </div> ); } export default ThemeToggler;
Como o valor do contexto do nosso tema agora é um gancho sempre que chamamos useContext
nele, ele retornará um array. Usando a desestruturação, conseguimos pegar os elementos do array. Em seguida, escrevemos um manipulador de eventos onClick
para nosso ThemeToggler
. Com esse código, sempre que o seletor de tema for clicado, ele mudará o tema da nossa aplicação.
Agora vamos editar as diferentes versões do nosso componente Main
.
Editando nosso componente MainWithClass
- A versão do componente
MainWithClass
que usa o métodoClass.contextType
:import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context[0]]; return( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } }
- A versão do componente
MainWithClass
que usa o métodoThemeContext.Consumer
:import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component { constructor() { super(); this.state = {} } render() { return ( <ThemeContext.Consumer> { ([theme]) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } } export default Main;
Editando nosso componente MainWithFunction
O componente MainWithFunction
deve ser editado da seguinte forma:
import React, { useContext } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; const Main = () => { const theme = useContext(ThemeContext)[0]; const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;
Conclusão
É isso! Conseguimos implementar dois modos de tema para nosso aplicativo React usando a Context API.
No processo, aprendemos:
- O que é a Context API e o problema que ela resolve;
- Quando usar a API de Contexto;
- Criando
Context
e consumindo-o em componentes funcionais e baseados em classe.
Leitura adicional no SmashingMag:
- Estilizando em aplicativos Web modernos
- Criando aplicativos móveis com Ionic e React
- Crie um PWA com Webpack e Workbox
- Conhecendo a API MutationObserver