Olá👋🏻,
Este artigo foi criado especificamente para iniciantes que desejam aprender métodos mais eficazes para gerenciar o estado entre vários componentes. Ele também visa resolver o problema comum de perfuração de suporte, que pode tornar seu código mais difícil de manter e entender. Vamos começar explicando que tipo de problema a API de contexto resolve.
Se preferir o formato de vídeo, aqui está o tutorial que você pode assistir no meu canal no YouTube.👇🏻
Você sabe como às vezes você precisa passar dados de um componente pai para um componente filho e acaba passando adereços por meio de vários componentes intermediários? Isso é chamado de perfuração de hélice e pode ficar confuso rapidamente. Vejamos um exemplo para esclarecer isso.
Conforme mostrado no diagrama, imagine que você buscou alguns dados no componente App
, que fica na raiz do seu aplicativo. Agora, se um componente profundamente aninhado, digamos o componente Grandchild
, precisar acessar esses dados, você normalmente os transmitiria através dos componentes Parent
e Child
como adereços antes de chegar Grandchild
. Isso pode ficar feio à medida que seu aplicativo cresce.
Aqui está outra representação visual:
No exemplo acima, o componente Profile
precisa de dados do usuário, mas esses dados precisam passar primeiro pelos componentes App
e Navigation
, mesmo que esses componentes intermediários não usem os dados em si. Então, como podemos limpar isso? É aí que a API Context se torna útil.
Perfuração de adereços:
A API de contexto em React.js permite passar dados entre componentes sem a necessidade de passá-los como acessórios em cada nível da árvore de componentes. Funciona como um sistema de gerenciamento de estado global onde você define seu estado em um objeto de contexto e pode acessá-lo facilmente em qualquer lugar na árvore de componentes. Vamos entender isso com um exemplo.
Como você pode ver no diagrama, temos um objeto de contexto que armazena dados que serão acessados por múltiplos componentes. Esses dados são obtidos de APIs ou serviços de terceiros. Antes de acessar esses dados de contexto em qualquer componente, precisamos agrupar todos os componentes que requerem esses dados em um componente provedor de contexto.
Se precisarmos apenas acessar dados nos componentes de navegação e perfil, não precisaremos finalizar o componente app. Depois de agrupar os componentes relevantes com ContextProvider
, você poderá acessar diretamente os dados de contexto em qualquer componente que os consuma. Não se preocupe se ainda não entendeu; vamos mergulhar no código e vê-lo em ação.
Primeiro, vamos criar um aplicativo React usando Vite.js. Basta copiar os seguintes comandos para configurar o projeto.
npm create vite@latest
cd project_name // to change to project directory npm install npm run dev
Então você pode abrir seu servidor de desenvolvimento http://localhost:5173
em seu navegador.
Primeiro, vamos criar as pastas necessárias. Aqui está a estrutura de pastas do nosso projeto.
src | components | context
Na pasta de componentes vamos criar o arquivo Profile.jsx
e adicionar o seguinte código.
import React from 'react' const Profile = () => { return ( <div>Profile</div> ) } export default Profile
Crie mais um componente chamado Navbar.jsx
na pasta de componentes.
import Profile from './Profile' const Navbar = () => { return ( <nav style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "90%", height: "10vh", backgroundColor: theme === "light" ? "#fff" : "#1b1b1b", color: theme === "light" ? "#1b1b1b" : "#fff", border: "1px solid #fff", borderRadius: "5px", padding: "0 20px", marginTop: "40px", }}> <h1>LOGO</h1> <Profile /> </nav> ) } export default Navbar
Vamos importar este componente <Navbar />
no arquivo App.jsx
.
import Navbar from "./components/Navbar"; function App() { return ( <main style={{ display: "flex", flexDirection: "column", justifyContent: "start", alignItems: "center", height: "100vh", width: "100vw", }} > <Navbar /> </main> ); } export default App;
Então, basicamente, o componente <Profile />
é filho de <Navbar />
e <Navbar />
é filho do componente <App />
.
Vamos criar o arquivo UserContext.jsx
na pasta context
. Adicione o seguinte código ao arquivo.
import { createContext, useEffect, useState } from "react"; export const UserContext = createContext(); export const UserProvider = ({ children }) => { const [user, setUser] = useState(null); const fetchUserData = async (id) => { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${id}` ).then((response) => response.json()); console.log(response); setUser(response); }; useEffect(() => { fetchUserData(1); }, []); return ( <UserContext.Provider value={{ user, fetchUserData }} > {children} </UserContext.Provider> ); };
UserContext
vazio usando createContext
. Certificamo-nos de importá-lo de react
. Podemos adicionar valores padrão dentro do objeto de contexto, mas o mantemos nulo por enquanto.
UserProvider
, que retorna um provedor usando UserContext
, como UserContext.Provider
. Ele envolve os componentes filhos e, no valor, podemos passar qualquer coisa que quisermos usar nos componentes filhos.
fetchUserData
aceita id
e usa esse ID para buscar os dados do usuário. Em seguida, armazenamos a resposta no estado user
.
fetchUserData
no useEffect
, portanto, no carregamento da página, ele chama a função e injeta os dados no estado user
.
Agora, vamos usar esse contexto no componente <App />
. Envolva o componente <NavBar />
usando <UserProvider />
; o mesmo que o seguinte código:
<UserProvider> <Navbar /> </UserProvider>
Vamos usar o estado user
no componente <Profile />
. Para isso, usaremos o gancho useContext
. Isso pega UserContext
e fornece os valores que passamos no UserProvider
como estado user
e função fetchUserData
. Lembre-se, não precisamos agrupar o componente <Profile />
pois ele já está no componente <Navbar />
que já está agrupado com o provedor.
Abra o Profile.jsx
e adicione o código a seguir.
const { user } = useContext(UserContext); if (user) { return ( <span style={{ fontWeight: "bold", }} > {user.name} </span> ); } else { return <span>Login</span>; }
Aqui, estamos usando o estado user
do UserContext
. Exibiremos o nome de usuário se houver user
, caso contrário, exibiremos apenas uma mensagem de login. Agora, se você vir a saída, deverá haver um nome de usuário no componente da barra de navegação. É assim que podemos usar diretamente qualquer estado que esteja no contexto de qualquer componente. O componente que usa esse estado deve ser agrupado em <Provider />
.
Você também pode usar vários contextos. Você só precisa agrupar os componentes do provedor em outro componente do provedor, conforme mostrado no exemplo a seguir.
<ThemeProvider> <UserProvider> <Navbar /> </UserProvider> </ThemeProvider>
No exemplo acima, estamos usando <ThemeProvider />
que gerencia o estado do tema.
Você pode assistir ao vídeo do YouTube acima para ver o exemplo completo do uso de vários provedores de contexto.
Há um problema que ocorre quando você usa a API Context em vários componentes. Sempre que o estado ou valor muda na API de contexto, ela renderiza novamente todos os componentes inscritos naquele contexto específico, mesmo que nem todos os componentes estejam usando o estado alterado. Para entender esse problema de nova renderização, vamos criar um componente <Counter />
que usa contexto para armazenar e exibir valores de contagem.
Confira o exemplo a seguir. Você pode criar um arquivo Counter.jsx
na pasta de componentes e colar o código a seguir.
import { createContext, memo, useContext, useState } from "react"; const CountContext = createContext(); const CountProvider = ({ children }) => { const [count, setCount] = useState(0); return ( <CountContext.Provider value={{ count, setCount }}> {children} </CountContext.Provider> ); }; function CountTitle() { console.log("This is Count Title component"); return <h1>Counter Title</h1>; } function CountDisplay() { console.log("This is CountDisplay component"); const { count } = useContext(CountContext); return <div>Count: {count}</div>; } function CounterButton() { console.log("This is CounterButton component"); const { count, setCount } = useContext(CountContext); return ( <> <CountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </> ); } export default function Counter() { return ( <CountProvider> <CounterButton /> </CountProvider> ); }
No código acima:
CountContext
usando createContext.
CountProvider,
temos um estado para armazenar valores de contagem. Estamos enviando count
e o método setCount
para os componentes filhos por meio da proposta de valor.
Criamos componentes separadamente para ver quantas vezes os componentes individuais são renderizados novamente.
<CountTitle />
: Este componente exibe apenas o título e nem mesmo usa nenhum valor do contexto.
<CountDisplay />
: Este componente exibe valores de contagem e usa o estado count
do contexto.
<CounterButton />
: Este componente renderiza o componente acima e um botão que aumenta os valores de contagem usando setCount
.
Ao final, estamos agrupando o componente <CounterButton />
dentro do componente CountProvider
para que os demais componentes possam acessar os valores de contagem.
Agora, se você executar o código e clicar no botão Increase
, verá nos logs que cada componente é renderizado novamente cada vez que o estado muda. O <CountTitle />
nem mesmo está usando valores de contagem, mas está sendo renderizado novamente. Isso está acontecendo porque o componente pai de <CountTitle />
que é <CounterButton />
está usando e atualizando o valor de count e é por isso que está sendo renderizado novamente.
Como podemos otimizar esse comportamento? A resposta é memo
. O memo
React permite pular a nova renderização de um componente quando seus adereços permanecem inalterados. Após o componente <CountTitle />
, vamos adicionar a seguinte linha.
const MemoizedCountTitle = React.memo(CountTitle)
Agora, no componente <CounterButton />
, onde estamos renderizando o componente <CountTitle />
, substitua <CountTitle />
por <MemoizedCountTitle />
como no código a seguir:
<> <MemoizedCountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </>
Agora, se você aumentar a contagem e verificar os logs, poderá ver que ele não está mais renderizando o componente <CountTitle />
.
O Redux
é uma biblioteca de gerenciamento de estado para gerenciamento de estado complexo com transições de estado mais previsíveis. Enquanto a API Context foi projetada para gerenciamento simples de estado e passagem de dados pela árvore de componentes sem perfuração de suporte. Então, quando escolher qual?
Há também mais uma biblioteca que também é uma opção popular para gestão estadual. O recuo da reação .
Se você estiver interessado em aprender mais sobre o React Recoil , deixe-me saber nos comentários e criarei tutoriais detalhados sobre este tópico com base no seu feedback.
A API de contexto React.js
oferece uma maneira poderosa e eficiente de gerenciar estados em vários componentes, abordando efetivamente o problema de perfuração de suporte. Ao usar a API Context, você pode simplificar seu código, reduzir novas renderizações desnecessárias e melhorar o desempenho geral do aplicativo.
Embora a API Context seja ideal para gerenciamento simples de estado, aplicativos mais complexos podem se beneficiar do uso Redux
ou de outras bibliotecas de gerenciamento de estado, como React Recoil
. Compreender quando e como usar essas ferramentas permitirá que você crie aplicativos React mais sustentáveis e escaláveis.
Obrigado por ler este artigo, espero que você tenha achado útil. Se você estiver interessado em aprender e construir projetos usando React, Redux e Next.js, pode visitar meu canal no YouTube aqui: CodeBucks
Aqui estão meus outros artigos que você pode gostar de ler:
Visite meu blog pessoal: DevDreaming