Uma introdução à API de contexto do React

Publicados: 2022-03-10
Resumo rápido ↬ Neste artigo, você aprenderá como usar a Context API do React, que permite gerenciar estados globais de aplicativos em seus aplicativos React sem recorrer ao detalhamento de props.

Para 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 →

Mais depois do salto! Continue lendo abaixo ↓

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:

  1. O primeiro método é por meio de Class.contextType .

    Para usar esse método, atribuímos o objeto context de nosso ThemeContext à propriedade contextType de nossa classe. Depois disso, poderemos acessar o valor de contexto usando this.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 atribuir ThemeContext à propriedade contextType de nossa classe, salvei o objeto de tema atual na variável currentTheme .

    Agora, vamos pegar as cores da variável currentTheme 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.
  2. 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 componente MainWithClass 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 nosso ThemeContext que chamamos de “tema” e pegamos os valores de cor para esse modo de tema e o atribuímos à variável currentTheme . 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

  1. A versão do componente MainWithClass que usa o método Class.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> ); } }
  2. A versão do componente MainWithClass que usa o método ThemeContext.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