React: Contexto e estado global
Última atualização: 2026-08-26
1. O que você vai aprender
- createContext e Provider
- useContext: contexto de consumo
- Exercícios de contexto versus exercícios de props
- Aninhamento em vários níveis e otimização de desempenho
- Combinando Context e useReducer (Gerenciamento leve de estado)
2. Uma história sobre a troca de temas
(1) Desafio: Passagem de props por 5 níveis de componentes
Alice quer adicionar um recurso de ativação/desativação do “modo escuro” ao aplicativo. O fluxo de dados deve passar por cinco camadas de componentes:
▶ Exemplo: Como usar o contexto
// Requirements:User switches to dark mode,All child components change accordingly
// Layer 5: innermost Button needs to Know theme
function Button({ theme }) {
return <button style={{
backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
color: theme === 'dark' ? 'white' : '#333'
}}>Button</button>
}
// Layer 4
function Sidebar({ theme }) {
return <div><Button theme={theme} /></div>
}
// Layer 3
function MainLayout({ theme }) {
return <div><Sidebar theme={theme} /></div>
}
// Layer 2
function Page({ theme }) {
return <MainLayout theme={theme} />
}
// Layer 1: App has theme state
function App() {
const [theme, setTheme] = useState('light')
return <Page theme={theme} /> // ❌ theme penetrates 5 layers
}
Pergunta da Alice:
themeÀ medida que os dados são transmitidos do aplicativo, passando por cada camada até o botão, as três camadas intermediárias, na verdade, não precisam desses dados- Se adicionarmos mais uma informação do usuário para ser transmitida, as propriedades ficarão extremamente pesadas.
- Este é o clássico treinamento com adereços
(2) Solução para “Contexto”
// 1. Create Context
const ThemeContext = React.createContext('light')
// 2. Provider Provide data
function App() {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
)
}
// The middle component is completely unconcerned with theme
function Page() { return <MainLayout /> }
function MainLayout() { return <Sidebar /> }
function Sidebar() { return <Button /> }
// 5. Only the necessary components are loaded Context
function Button() {
const { theme, setTheme } = useContext(ThemeContext)
return (
<button style={{
backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
color: theme === 'dark' ? 'white' : '#333'
}}
onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>
{theme === 'dark' ? '🌙' : '☀️'} Switch Themes
</button>
)
}
Benefícios: Os componentes intermediários (Page, MainLayout, Sidebar) não precisam mais passar os props theme; os dados são passados diretamente do Provider para o Consumer.
3. Conceitos fundamentais do contexto
| API | Função | Parâmetros/Valores de retorno |
|---|---|---|
createContext(defaultValue) |
Criar um contêiner de contexto | Voltar para { Provider, Consumer } |
<Context.Provider value={...}> |
Fornece dados para a subárvore | value: Qualquer tipo; aciona uma nova renderização do Consumer quando sofre alterações |
useContext(Context) |
Recuperar os dados mais recentes do Provedor | Retornar o value do Provedor; se nenhum Provedor for encontrado, retornar o valor padrão |
Hook personalizado useXxx() |
Envolve o useContext + verificação de erros | Padrão recomendado: lança um erro claro quando o Provider está ausente |
(1) createContext + Provider + useContext
graph TB
A[createContext<br/>Create a context container] --> B[Provider<br/>Wrapper Component tree<br/>Provide value]
B --> C[Middle Component<br/>Don't care Context]
C --> D[useContext<br/>Consumption Context Data]
style A fill:#1890ff,color:#fff
style B fill:#52c41a,color:#fff
style D fill:#ff6b6b,color:#fff
// 1. Create Context(You can specify a default value)
const UserContext = createContext({ name: 'Guest', role: 'guest' })
const ThemeContext = createContext('light')
// 2. Provider Provide data(The component tree that the wrapper needs to access)
function App() {
const [user, setUser] = useState({ name: 'Alice', role: 'admin' })
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value="dark">
<Dashboard />
</ThemeContext.Provider>
</UserContext.Provider>
)
}
// 3. Any child component reads Context
function Dashboard() {
const { user } = useContext(UserContext)
const theme = useContext(ThemeContext)
return <p>{user.name} - Topic:{theme}</p>
}
(2) Cenários em que o contexto é aplicável
| Adequado para o contexto | Não adequado para o contexto |
|---|---|
| Tema (Modo Escuro) | Parâmetros de configuração dos componentes (usando Props) |
| Informações de login do usuário | Valores inseridos no formulário (usando estado local) |
| Idioma/Internacionalização | Dados da API (usando o TanStack Query) |
| Estado da rota | Estado da animação (gerenciado pela biblioteca de animação) |
| Configurações globais | Passagem simples de propriedades de pai para filho (usando propriedades diretamente) |
Regra: Use Context apenas quando for necessário compartilhar dados entre “vários níveis e vários componentes”. Se você estiver simplesmente passando valores entre componentes pai e filho, os Props são suficientes.
4. Aninhamento de contextos e modularização de provedores
(1) Aninhamento de contextos em vários níveis
Os projetos do mundo real geralmente apresentam vários contextos:
function App() {
return (
<AuthProvider>
<ThemeProvider>
<I18nProvider>
<NotificationProvider>
<MainApp />
</NotificationProvider>
</I18nProvider>
</ThemeProvider>
</AuthProvider>
)
}
Para evitar o caos do aninhamento, você pode criar um componente composto AppProviders:
function AppProviders({ children }) {
return (
<AuthProvider>
<ThemeProvider>
<I18nProvider>
<NotificationProvider>
{children}
</NotificationProvider>
</I18nProvider>
</ThemeProvider>
</AuthProvider>
)
}
function App() {
return (
<AppProviders>
<MainApp />
</AppProviders>
)
}
(2) Hook de provedor personalizado
// ============================================
// Example:Custom ThemeProvider + useTheme Hook
// ============================================
const ThemeContext = React.createContext(null)
function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState(() => {
// from localStorage Read the initial topic
return localStorage.getItem('theme') || 'light'
})
function toggleTheme() {
setTheme(prev => {
const next = prev === 'light' ? 'dark' : 'light'
localStorage.setItem('theme', next) // Persistence
return next
})
}
const value = React.useMemo(
() => ({ theme, toggleTheme }),
[theme]
)
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
)
}
// Custom Hook:Packaging useContext + Error Checking
function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme Must be ThemeProvider For internal use only')
}
return context
}
// Usage
function ThemedButton() {
const { theme, toggleTheme } = useTheme()
return (
<button onClick={toggleTheme}
style={{
backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
color: theme === 'dark' ? 'white' : '#333',
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}>
Current Topic:{theme === 'dark' ? '🌙' : '☀️'} Click to toggle
</button>
)
}
5. Otimização do desempenho em contexto
Quando o valor de um Contexto muda, todos os componentes que utilizam esse Contexto são renderizados novamente. Essa é a maior armadilha de desempenho dos Contextos.
| Método de otimização | Princípio | Cenários aplicáveis |
|---|---|---|
useMemo Valor estável |
Evite criar novas referências a objetos a cada renderização | Quando o valor de um Provider contém uma função ou um objeto |
| Divisão por contexto | Dados “mutáveis” e “imutáveis” são atribuídos a contextos diferentes | Alguns consumidores precisam apenas de dados estáveis |
React.memo Envolvendo o Consumer |
Ignorando a re-renderização quando os props não mudaram | Quando o componente Consumer recebe outros props |
| Assinatura seletiva | Use um seletor para recuperar apenas as fatias de que você precisa | Implemente atualizações precisas com Zustand/Redux |
// ❌ Question:Every time App Rendering,value They're all new partners. → All Consumer All are heavily rendered
function App() {
const [count, setCount] = useState(0)
return (
<ThemeContext.Provider value={{ theme: 'dark', setCount }}>
<ExpensiveTree />
</ThemeContext.Provider>
)
}
// ✅ Fix 1:use useMemo Stable value Quote
function App() {
const [count, setCount] = useState(0)
const themeValue = useMemo(
() => ({ theme: 'dark', toggleTheme: () => setCount(c => c + 1) }),
[]
)
return (
<ThemeContext.Provider value={themeValue}>
<ExpensiveTree />
</ThemeContext.Provider>
)
}
// ✅ Fix 2:separate "unchanged" and "changed" data into different Context
const SettingsContext = createContext({ theme: 'light', language: 'zh' })
const CountContext = createContext(0)
function App() {
const [count, setCount] = useState(0)
return (
<SettingsContext.Provider value={settingsValue}> {/* useMemo Stable */}
<CountContext.Provider value={count}>
<ExpensiveTree />
</CountContext.Provider>
</SettingsContext.Provider>
)
}
// Only when used CountContext The component will be count Re-render on changes
// SettingsContext Consumers will not be affected
6. Context + useReducer: Criando um estado global leve
Ao combinar a “propagação entre níveis” do Context com o “gerenciamento complexo de estado” do useReducer, é possível criar uma solução leve para o estado global:
// ============================================
// Example:Global Notification System(Context + useReducer)
// ============================================
// ---- 1. Create Context ----
const NotificationContext = createContext(null)
// ---- 2. Definition reducer ----
function notificationReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, {
id: Date.now(),
message: action.message,
type: action.notifType || 'info',
read: false
}]
case 'MARK_READ':
return state.map(n =>
n.id === action.id ? { ...n, read: true } : n
)
case 'REMOVE':
return state.filter(n => n.id !== action.id)
case 'CLEAR_ALL':
return []
default:
return state
}
}
// ---- 3. Provider Components ----
function NotificationProvider({ children }) {
const [notifications, dispatch] = useReducer(notificationReducer, [])
// use useMemo Stable value
const value = useMemo(() => ({ notifications, dispatch }), [notifications])
return (
<NotificationContext.Provider value={value}>
{children}
</NotificationContext.Provider>
)
}
// ---- 4. Custom Hook ----
function useNotification() {
const context = useContext(NotificationContext)
if (!context) throw new Error('useNotification Must be NotificationProvider For internal use')
return context
}
// ---- 5. Using the notification component ----
function NotificationBell() {
const { notifications, dispatch } = useNotification()
const unread = notifications.filter(n => !n.read).length
return (
<div style={{ position: 'relative', cursor: 'pointer' }}>
🔔
{unread > 0 && (
<span style={{
position: 'absolute', top: '-8px', right: '-8px',
backgroundColor: '#ff4d4f', color: 'white',
borderRadius: '50%', width: '18px', height: '18px',
fontSize: '12px', display: 'flex', alignItems: 'center',
justifyContent: 'center'
}}>
{unread}
</span>
)}
</div>
)
}
function NotificationList() {
const { notifications, dispatch } = useNotification()
if (notifications.length === 0) {
return <p style={{ color: '#999', textAlign: 'center' }}>No notice at this time</p>
}
return (
<div style={{ maxWidth: '400px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<strong>Notice({notifications.length})</strong>
<button onClick={() => dispatch({ type: 'CLEAR_ALL' })} style={smallBtn}>Clear All</button>
</div>
{notifications.map(n => (
<div key={n.id} style={{
padding: '8px', marginBottom: '4px',
backgroundColor: n.read ? '#fafafa' : '#e6f7ff',
borderRadius: '4px', cursor: 'pointer',
display: 'flex', justifyContent: 'space-between',
alignItems: 'center'
}}
onClick={() => dispatch({ type: 'MARK_READ', id: n.id })}>
<span>
{n.type === 'error' ? '❌' : n.type === 'success' ? '✅' : 'ℹ️'}
{n.message}
</span>
<button onClick={e => { e.stopPropagation(); dispatch({ type: 'REMOVE', id: n.id }) }}
style={{ ...smallBtn, color: '#ff4d4f' }}>✕</button>
</div>
))}
</div>
)
}
// ---- The component that sends notifications ----
function NotificationSender() {
const { dispatch } = useNotification()
const [message, setMessage] = useState('')
function send(type) {
if (!message.trim()) return
dispatch({ type: 'ADD', message: message.trim(), notifType: type })
setMessage('')
}
return (
<div>
<input value={message} onChange={e => setMessage(e.target.value)}
placeholder="Notice Content" style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
<div style={{ display: 'flex', gap: '4px' }}>
<button onClick={() => send('info')} style={typeBtn('#1890ff')}>ℹ️ info</button>
<button onClick={() => send('success')} style={typeBtn('#52c41a')}>✅ Success</button>
<button onClick={() => send('error')} style={typeBtn('#ff4d4f')}>❌ Error</button>
</div>
</div>
)
}
const smallBtn = { padding: '2px 8px', fontSize: '12px', border: 'none', backgroundColor: 'transparent', cursor: 'pointer' }
const typeBtn = (color) => ({ padding: '6px 12px', backgroundColor: color, color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' })
// ---- 6. Assembly ----
function NotificationApp() {
return (
<NotificationProvider>
<div style={{ maxWidth: '500px', margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2>🔔 Notification System</h2>
<NotificationBell />
</div>
<NotificationSender />
<hr style={{ margin: '16px 0' }} />
<h3>List of Notices</h3>
<NotificationList />
</div>
</NotificationProvider>
)
}
Resultado esperado: Um sistema de notificações completo. Um ícone de notificação (exibindo o número de notificações não lidas) aparece na parte superior; no meio, os usuários podem enviar três tipos de notificações: informação, sucesso e erro; e na parte inferior, é exibida uma lista de notificações, na qual cada notificação pode ser marcada como lida ou excluída.
▶ Exemplo 2: Gerenciamento do status de autenticação do usuário (Contexto + Hook personalizado)
// ============================================
// Example:User Authentication System——Context + Custom Hook Best Practices
// Features:Admin Login/Logged Out,Provide user information globally
// ============================================
import { createContext, useContext, useState, useCallback, useMemo } from 'react'
// ---- 1. Create Context ----
const AuthContext = createContext(null)
// ---- 2. Provider Components ----
function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(false)
const login = useCallback(async (username, password) => {
setLoading(true)
try {
// Simulate a login request
await new Promise(resolve => setTimeout(resolve, 800))
if (username && password) {
const loggedInUser = {
id: 1,
name: username,
email: `${username}@example.com`,
avatar: 'https://i.pravatar.cc/40',
role: username === 'admin' ? 'Administrator' : 'Regular User'
}
setUser(loggedInUser)
localStorage.setItem('user', JSON.stringify(loggedInUser))
return { success: true }
}
return { success: false, error: 'Incorrect username or password' }
} finally {
setLoading(false)
}
}, [])
const logout = useCallback(() => {
setUser(null)
localStorage.removeItem('user')
}, [])
// use useMemo Stable value Quote,Avoid Provider Render all child components again
const value = useMemo(() => ({
user,
loading,
isAuthenticated: !!user,
login,
logout
}), [user, loading, login, logout])
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
// ---- 3. Custom Hook + Error Checking ----
function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth Must be AuthProvider For internal use only')
}
return context
}
// ---- 4. Use Certified Components ----
function LoginForm() {
const { login, loading } = useAuth()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
async function handleSubmit(e) {
e.preventDefault()
setError('')
const result = await login(username, password)
if (!result.success) {
setError(result.error)
}
}
return (
<form onSubmit={handleSubmit} style={{ maxWidth: '400px', margin: '0 auto' }}>
<h2>🔐 Log In</h2>
{error && <p style={{ color: '#ff4d4f', fontSize: '14px' }}>{error}</p>}
<input value={username} onChange={e => setUsername(e.target.value)}
placeholder="Username(Input admin Experience the Role of an Administrator)"
style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)}
placeholder="Password"
style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
<button type="submit" disabled={loading}
style={{ width: '100%', padding: '10px', backgroundColor: '#1890ff', color: 'white', border: 'none', borderRadius: '4px', cursor: loading ? 'not-allowed' : 'pointer' }}>
{loading ? 'Logging in......' : 'Log In'}
</button>
</form>
)
}
function UserDashboard() {
const { user, logout } = useAuth()
return (
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
<img src={user.avatar} alt={user.name}
style={{ width: '40px', height: '40px', borderRadius: '50%' }} />
<div>
<h3 style={{ margin: 0 }}>{user.name}</h3>
<p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '13px' }}>
{user.email} · {user.role}
</p>
</div>
</div>
<button onClick={logout}
style={{ padding: '8px 16px', backgroundColor: '#ff4d4f', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
Log Out
</button>
</div>
)
}
// ---- 5. Top-level component ----
function AuthApp() {
const { isAuthenticated } = useAuth()
return isAuthenticated ? <UserDashboard /> : <LoginForm />
}
// ---- 6. Entrance ----
function App() {
return (
<AuthProvider>
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
<AuthApp />
</div>
</AuthProvider>
)
}
▶ Exemplo 3: Combinação de várias camadas de provedores com a internacionalização (i18n)
const I18nContext = createContext(null)
function I18nProvider({ children }) {
const [locale, setLocale] = useState('zh')
const translations = {
zh: { title: 'App Title', greeting: 'Hello', logout: 'Exit' },
en: { title: 'App Title', greeting: 'Hello', logout: 'Logout' },
}
const t = useCallback((key) => translations[locale][key] || key, [locale])
const value = useMemo(() => ({ locale, setLocale, t }), [locale, t])
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
}
function useI18n() {
const ctx = useContext(I18nContext)
if (!ctx) throw new Error('useI18n must be inside I18nProvider')
return ctx
}
function LocaleSwitcher() {
const { locale, setLocale } = useI18n()
return (
<select value={locale} onChange={e => setLocale(e.target.value)}
style={{ padding: '4px 8px', borderRadius: 4 }}>
<option value="zh">Chinese</option>
<option value="en">English</option>
</select>
)
}
function Greeting() {
const { t } = useI18n()
return <h2>{t('greeting')}</h2>
}
function I18nApp() {
return (
<I18nProvider>
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Greeting />
<LocaleSwitcher />
</div>
</div>
</I18nProvider>
)
}
▶ Exemplo 4: Divisão do contexto para otimizar o desempenho
const UserStateContext = createContext(null)
const UserDispatchContext = createContext(null)
function UserProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(false)
const dispatch = useMemo(() => ({
login: async (name) => {
setLoading(true)
await new Promise(r => setTimeout(r, 500))
setUser({ name, role: name === 'admin' ? 'Admin' : 'User' })
setLoading(false)
},
logout: () => setUser(null),
}), [])
const stateValue = useMemo(() => ({ user, loading }), [user, loading])
return (
<UserDispatchContext.Provider value={dispatch}>
<UserStateContext.Provider value={stateValue}>
{children}
</UserStateContext.Provider>
</UserDispatchContext.Provider>
)
}
function useUserState() { return useContext(UserStateContext) }
function useUserDispatch() { return useContext(UserDispatchContext) }
function LoginButton() {
const { login } = useUserDispatch()
const { loading } = useUserState()
return <button disabled={loading} onClick={() => login('Alice')}>Login</button>
}
function UserInfo() {
const { user } = useUserState()
if (!user) return <p>Not logged in</p>
return <p>Logged in as {user.name} ({user.role})</p>
}
function SplitContextApp() {
return (
<UserProvider>
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<UserInfo />
<LoginButton />
</div>
</UserProvider>
)
}
▶ Exemplo 5: Exemplo completo do uso de useContext como alternativa ao treinamento com adjetivos
const SettingsContext = createContext(null)
function SettingsProvider({ children }) {
const [settings, setSettings] = useState({
theme: 'light', fontSize: 14, sidebarCollapsed: false,
})
const updateSetting = useCallback((key, value) => {
setSettings(prev => ({ ...prev, [key]: value }))
}, [])
const value = useMemo(() => ({ settings, updateSetting }), [settings, updateSetting])
return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>
}
function useSettings() {
const ctx = useContext(SettingsContext)
if (!ctx) throw new Error('useSettings must be inside SettingsProvider')
return ctx
}
function Toolbar() {
const { settings, updateSetting } = useSettings()
return (
<div style={{ padding: 8, background: settings.theme === 'dark' ? '#333' : '#f0f0f0', display: 'flex', gap: 8, alignItems: 'center' }}>
<button onClick={() => updateSetting('theme', settings.theme === 'dark' ? 'light' : 'dark')}>
{settings.theme === 'dark' ? '☀️' : '🌙'}
</button>
<button onClick={() => updateSetting('sidebarCollapsed', !settings.sidebarCollapsed)}>
{settings.sidebarCollapsed ? '▶' : '◀'} Sidebar
</button>
<select value={settings.fontSize} onChange={e => updateSetting('fontSize', Number(e.target.value))}>
<option value={12}>12px</option>
<option value={14}>14px</option>
<option value={16}>16px</option>
<option value={18}>18px</option>
</select>
</div>
)
}
function Content() {
const { settings } = useSettings()
return (
<div style={{ fontSize: settings.fontSize, color: settings.theme === 'dark' ? '#eee' : '#333',
background: settings.theme === 'dark' ? '#1a1a1a' : '#fff', padding: 16 }}>
<p>Current settings:</p>
<ul>
<li>Theme: {settings.theme}</li>
<li>Font size: {settings.fontSize}px</li>
<li>Sidebar: {settings.sidebarCollapsed ? 'collapsed' : 'expanded'}</li>
</ul>
</div>
)
}
function SettingsApp() {
return (
<SettingsProvider>
<div style={{ maxWidth: 600, margin: '0 auto' }}>
<Toolbar />
<Content />
</div>
</SettingsProvider>
)
}
❓ Perguntas Frequentes
P: Qual é a diferença entre Context e Redux/Zustand? Quando se deve usar cada um? R: O Context é o mecanismo de injeção de dependências integrado ao React — ele permite que os dados sejam passados entre níveis. Não é uma biblioteca de gerenciamento de estado! Redux/Zustand são bibliotecas de gerenciamento de estado com um fluxo de dados completo e previsível: estado → dispatch → redutor. Use o Context quando sua única necessidade for “evitar o prop drilling”. Use o Zustand/Redux quando precisar de “operações complexas de estado, middleware e DevTools”.
P: O que devo fazer se o
Contextdovaluefor um objeto novo a cada vez, fazendo com que todos osConsumers sejam renderizados novamente? R: UseuseMemopara estabilizar a referência dovalue:const value = useMemo(() => ({ theme, toggleTheme }), [theme]). Você também pode separar os dados “estáveis” e “em mudança” em diferentesContexts. Se os problemas de desempenho persistirem após a otimização, considere usar o Zustand (que suporta atualizações precisas e não aciona a re-renderização de componentes não relacionados).
P: O que devo fazer se houver muitas camadas de Contextos aninhados? R: ① Use um Provider para agrupar componentes (como o
AppProvidersmencionado anteriormente) e encapsular o aninhamento; ② Avalie se você realmente precisa de tantos Contextos — alguns dados “globais” são, na verdade, apenas “localmente globais” (como configurações para uma página específica) e não precisam ser colocados no Contexto de nível superior do aplicativo.
P: Por que verificamos se
contexté nulo em um hook personalizadouseXxx? R: Trata-se de uma técnica de programação defensiva. Se um desenvolvedor se esquecer de envolver um componente com umProviderantes de usaruseContext,contextserá nulo (caso nenhum valor padrão tenha sido passado paracreateContext) ou causará erros difíceis de depurar. Exibir uma mensagem de erro clara logo no início é muito mais intuitivo do que fazer com que o usuário veja “Não é possível ler a propriedade de um valor nulo” no console do navegador.
P: O Context pode substituir o Redux? R: Sim, em projetos de pequeno a médio porte, mas não em projetos grandes. O Context não possui três recursos que o Redux oferece: ① o mecanismo de middleware (registro em log, operações assíncronas, persistência); ② depuração com “viagem no tempo” (reprodução de alterações de estado no DevTools); ③ assinatura seletiva (uma alteração em um valor do Context faz com que todos os Consumers sejam renderizados novamente, enquanto o Redux permite usar seletores para assinar apenas as partes específicas de que você precisa). Se o seu projeto exigir esses três recursos, use o Zustand (leve) ou o Redux Toolkit.
📖 Resumo
createContext→.Provider→useContextTrês etapas para usar o Context- O Context resolve o Props Drilling, mas não resolve o problema do “gerenciamento de estado”
- Armadilha de desempenho: Alterações no valor do contexto fazem com que todos os consumidores sejam renderizados novamente; otimize usando
useMemo - Personalizar o gancho
useXxx+ verificação de erros é uma prática recomendada Context + useReducerpode servir como uma solução leve para o estado global, mas, para cenários complexos, ainda é melhor usar o Zustand ou o Redux.
📝 Exercícios
- Problema básico (Dificuldade ⭐): Crie um hook
LocaleProvider+useLocaleque forneça os métodoslocale(zh/en) etoggleLocale()para permitir a alternância entre chinês e inglês. - Exercício avançado (Dificuldade ⭐⭐): Crie um hook
AuthProvider+useAuthque forneça os métodosuser,login(username, password)elogout()para gerenciar o status de autenticação do usuário. - Desafio (Dificuldade: ⭐⭐⭐): Crie um hook
CartProvider+useCarte use Context + useReducer para implementar um carrinho de compras com funções para adicionar, remover, alterar quantidades e esvaziar o carrinho. UseuseMemopara otimizar o código e evitar re-renderizações desnecessárias.