【React Native娓娓道来】第6课 状态管理的进阶之路:Context、useReducer 与跨组件共享状态
开篇:导航带来的新问题
第5课把单页面变成了多页面。但新问题随之而来:
用户在"设置"页把主题从亮色切成暗色,回到"首页"后,首页怎么知道主题变了?用户在"登录"页登录成功,其他所有页面怎么知道"当前用户是谁"?
最直接的办法:把状态放在最顶层 App 里,通过 props 一层一层往下传。但组件嵌套一深,就变成这样:
// 概念表达
App(持有 theme)
└── TabNavigator
└── HomeStack
└── HomeScreen
└── ArticleList
└── ArticleItem(需要 theme)
theme 从 App 一路传到 ArticleItem,中间每一层都要接收并转发,哪怕自己根本不用。这就是 prop drilling(属性层层透传)。
它带来三个问题:中间组件被迫接收无关 props;增删共享状态要改很多文件;组件复用变难。
这一课要解决的问题是:
如何让多个组件、多个页面共享同一份状态,而不必层层传 props?
答案:React 内置的 Context 和 useReducer。
一、Context:跨组件传递数据的通道
1.1 一个类比
Context 像一个公司内部的广播系统。总部发布一条通知,所有部门都能收到,不需要层层口头传达。
用 React 的话说:顶层组件把数据放进去,任意深度的后代组件都能直接取出来。
1.2 三步走
Context 的使用只有三步:
// 概念表达
createContext -> 创建通道 // 第一步
Provider -> 把数据放进去 // 第二步
useContext -> 从通道里取数据 // 第三步
1.3 创建 Context
// JavaScript + React Native
import { createContext } from 'react'; // 引入 createContext
const ThemeContext = createContext('light'); // 创建 Context,默认值为 'light'
createContext 接收一个默认值。这个默认值只在"没有 Provider 包裹"时生效。
1.4 提供数据
// JavaScript + React Native
import { createContext, useState } from 'react'; // 引入需要的 Hook
const ThemeContext = createContext('light'); // 创建 Context
export default function App() { // 定义并默认导出 App
const [theme, setTheme] = useState('light'); // 创建状态 theme,初始 'light'
return ( // 返回界面描述
<ThemeContext.Provider value={theme}> // Provider 提供 theme
{/* 所有后代组件都能读取 theme */} // 子组件
</ThemeContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 App
Provider 接收一个 value 属性,value 就是共享给后代的数据。
1.5 读取数据
// JavaScript + React Native
import { useContext } from 'react'; // 引入 useContext
function ArticleItem() { // 定义子组件
const theme = useContext(ThemeContext); // 读取 ThemeContext 的值
return ( // 返回界面描述
<Text style={{ color: theme === 'dark' ? '#fff' : '#000' }}> // 根据主题决定文字颜色
文章标题 // 文本内容
</Text> // 结束 Text
); // 结束 return
} // 结束 ArticleItem
ArticleItem 无论嵌套多深,都能拿到 theme,不需要中间组件传 props。
1.6 完整示例:主题切换
// JavaScript + React Native
import { createContext, useContext, useState } from 'react'; // 引入需要的 Hook
import { View, Text, Button, StyleSheet } from 'react-native'; // 引入需要的组件
const ThemeContext = createContext({ // 创建 ThemeContext
theme: 'light', // 默认主题
toggleTheme: () => {}, // 默认切换函数(占位)
}); // 结束 createContext
function ThemedText({ children }) { // 定义主题文字组件
const { theme } = useContext(ThemeContext); // 读取主题
return ( // 返回界面描述
<Text style={{ color: theme === 'dark' ? '#fff' : '#000' }}> // 根据主题决定颜色
{children} // 渲染子内容
</Text> // 结束 Text
); // 结束 return
} // 结束 ThemedText
function ThemeToggle() { // 定义主题切换组件
const { theme, toggleTheme } = useContext(ThemeContext); // 读取主题和切换函数
return ( // 返回界面描述
<Button // 按钮
title={theme === 'dark' ? '切换到亮色' : '切换到暗色'} // 按钮文字由主题决定
onPress={toggleTheme} // 点击调用切换函数
/> // 结束 Button
); // 结束 return
} // 结束 ThemeToggle
function Content() { // 定义内容组件
const { theme } = useContext(ThemeContext); // 读取主题
return ( // 返回界面描述
<View // 内容容器
style={[ // 组合样式
styles.content, // 基础样式
{ backgroundColor: theme === 'dark' ? '#222' : '#fff' }, // 根据主题决定背景色
]} // 结束样式数组
> // 结束开始标签
<ThemedText>这是主题化文本</ThemedText> // 使用主题文字
<ThemeToggle /> // 主题切换按钮
</View> // 结束容器
); // 结束 return
} // 结束 Content
export default function App() { // 定义并默认导出 App
const [theme, setTheme] = useState('light'); // 创建状态 theme,初始 'light'
const toggleTheme = () => { // 定义切换主题函数
setTheme((prev) => (prev === 'light' ? 'dark' : 'light')); // 用函数式更新翻转主题
}; // 结束 toggleTheme
return ( // 返回界面描述
<ThemeContext.Provider value={{ theme, toggleTheme }}> // Provider 提供主题和切换函数
<Content /> // 渲染内容组件
</ThemeContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 App
const styles = StyleSheet.create({ // 创建样式表
content: { flex: 1, justifyContent: 'center', alignItems: 'center' }, // 内容容器样式
}); // 结束样式表
App持有theme状态,通过 Provider 提供;Content、ThemedText、ThemeToggle都通过useContext直接读取;- 点击切换按钮,所有用到 theme 的组件自动重新渲染。
1.7 默认值为什么要写成对象
上面例子的默认值是 { theme, toggleTheme },而不是单个字符串 'light'。
因为组件从 Context 里读取的往往不只是数据,还有操作数据的函数。把数据和函数放在同一个对象里,使用起来更一致。默认值里的空函数只是占位,实际使用时会被 Provider 的 value 覆盖。
二、useReducer:管理复杂状态
2.1 useState 不够用的场景
useState 适合简单的状态。但当状态变得复杂时,会显得零散。
比如一个购物车:
// JavaScript + React Native
const [items, setItems] = useState([]); // 商品列表
const [total, setTotal] = useState(0); // 总价
const [loading, setLoading] = useState(false); // 加载状态
const [error, setError] = useState(null); // 错误状态
添加一件商品时,要同时更新 items 和 total。这两个状态逻辑上相关,分开管理容易出问题。
useReducer 把这些相关状态和更新逻辑集中到一个地方。
2.2 基本结构
// JavaScript + React Native
import { useReducer } from 'react'; // 引入 useReducer
import { View, Text, Button } from 'react-native'; // 引入需要的组件
const initialState = { count: 0 }; // 定义初始状态
function reducer(state, action) { // 定义 reducer 函数
switch (action.type) { // 根据 action 类型分发
case 'increment': // 加一
return { count: state.count + 1 }; // 返回新状态
case 'decrement': // 减一
return { count: state.count - 1 }; // 返回新状态
case 'reset': // 重置
return { count: 0 }; // 返回初始状态
default: // 未知 action
throw new Error(`未知 action: ${action.type}`); // 抛出错误
} // 结束 switch
} // 结束 reducer
function Counter() { // 定义计数器组件
const [state, dispatch] = useReducer(reducer, initialState); // 使用 useReducer
return ( // 返回界面描述
<View> // 外层容器
<Text>{state.count}</Text> // 显示计数
<Button title="加一" onPress={() => dispatch({ type: 'increment' })} /> // 点击发送 increment
<Button title="减一" onPress={() => dispatch({ type: 'decrement' })} /> // 点击发送 decrement
<Button title="重置" onPress={() => dispatch({ type: 'reset' })} /> // 点击发送 reset
</View> // 结束容器
); // 结束 return
} // 结束 Counter
reducer是一个纯函数,接收state和action,返回新状态;useReducer返回[state, dispatch];dispatch发送 action,reducer 根据action.type决定怎么更新。
2.3 reducer 必须是纯函数
三条铁律:相同输入得到相同输出;不修改原 state;不做副作用。
// JavaScript + React Native
// 错误:直接修改 state
function reducer(state, action) { // 定义 reducer
if (action.type === 'increment') { // 判断 action 类型
state.count = state.count + 1; // 直接修改 state,不允许
return state; // 返回同一个对象
} // 结束判断
} // 结束 reducer
// JavaScript + React Native
// 正确:创建新对象
function reducer(state, action) { // 定义 reducer
if (action.type === 'increment') { // 判断 action 类型
return { ...state, count: state.count + 1 }; // 用展开运算符创建新对象
} // 结束判断
} // 结束 reducer
2.4 action 携带数据
// JavaScript + React Native
dispatch({ type: 'add', text: '学习' }); // 发送带数据的 action
// JavaScript + React Native
function reducer(state, action) { // 定义 reducer
switch (action.type) { // 根据 action 类型分发
case 'add': // 添加待办
return { // 返回新状态
...state, // 展开原状态
todos: [...state.todos, { id: Date.now(), text: action.text }], // 追加新待办
}; // 结束返回
case 'remove': // 删除待办
return { // 返回新状态
...state, // 展开原状态
todos: state.todos.filter((t) => t.id !== action.id), // 过滤掉指定 id
}; // 结束返回
default: // 未知 action
return state; // 返回原状态
} // 结束 switch
} // 结束 reducer
所有更新逻辑集中在 reducer 中,一目了然。
2.5 什么时候用 useReducer
- 多个状态字段互相关联;
- 状态更新逻辑比较复杂;
- 下一步状态依赖上一步状态;
- 想把更新逻辑抽出来单独测试。
如果只是简单布尔值或数字,useState 就够。
三、Context + useReducer:跨组件状态管理
3.1 分工
useReducer负责"怎么更新状态";Context负责"把状态和 dispatch 提供给所有需要的组件"。
两者结合,形成一个轻量级的状态管理中心。
3.2 完整示例:待办事项
// JavaScript + React Native
import { createContext, useContext, useReducer, useState } from 'react'; // 引入需要的 Hook
import { // 引入需要的组件
View, // 容器
Text, // 文本
Button, // 按钮
TextInput, // 输入框
FlatList, // 高效列表
Pressable, // 可点击容器
StyleSheet, // 样式表
} from 'react-native'; // 从 react-native 导入
const TodoContext = createContext(null); // 创建 TodoContext
const initialState = { // 定义初始状态
todos: [ // 待办列表
{ id: '1', text: '学习 Context', done: false }, // 第一条
{ id: '2', text: '学习 useReducer', done: false }, // 第二条
], // 结束列表
}; // 结束 initialState
function reducer(state, action) { // 定义 reducer
switch (action.type) { // 根据 action 类型分发
case 'add': // 添加待办
return { // 返回新状态
...state, // 展开原状态
todos: [ // 新的待办列表
...state.todos, // 展开原列表
{ id: Date.now().toString(), text: action.text, done: false }, // 追加新待办
], // 结束列表
}; // 结束返回
case 'toggle': // 切换完成状态
return { // 返回新状态
...state, // 展开原状态
todos: state.todos.map((todo) => // 遍历列表
todo.id === action.id ? { ...todo, done: !todo.done } : todo // 匹配则翻转,否则原样
), // 结束 map
}; // 结束返回
case 'remove': // 删除待办
return { // 返回新状态
...state, // 展开原状态
todos: state.todos.filter((todo) => todo.id !== action.id), // 过滤掉指定 id
}; // 结束返回
default: // 未知 action
return state; // 返回原状态
} // 结束 switch
} // 结束 reducer
function TodoProvider({ children }) { // 定义 TodoProvider
const [state, dispatch] = useReducer(reducer, initialState); // 使用 useReducer
return ( // 返回界面描述
<TodoContext.Provider value={{ state, dispatch }}> // Provider 提供 state 和 dispatch
{children} // 渲染子组件
</TodoContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 TodoProvider
function useTodos() { // 定义自定义 Hook
const context = useContext(TodoContext); // 读取 Context
if (!context) { // 如果没有 Provider
throw new Error('useTodos 必须在 TodoProvider 内使用'); // 抛出错误
} // 结束判断
return context; // 返回 context
} // 结束 useTodos
function AddTodo() { // 定义添加组件
const [text, setText] = useState(''); // 创建状态 text
const { dispatch } = useTodos(); // 获取 dispatch
const handleAdd = () => { // 定义添加函数
if (text.trim() === '') return; // 空字符串不添加
dispatch({ type: 'add', text: text.trim() }); // 发送 add action
setText(''); // 清空输入框
}; // 结束 handleAdd
return ( // 返回界面描述
<View style={styles.addRow}> // 添加行容器
<TextInput // 输入框
style={styles.input} // 应用 input 样式
value={text} // 值由 text 决定
onChangeText={setText} // 输入变化时更新 text
placeholder="输入新事项" // 占位提示
/> // 结束 TextInput
<Button title="添加" onPress={handleAdd} /> // 添加按钮
</View> // 结束容器
); // 结束 return
} // 结束 AddTodo
function TodoList() { // 定义列表组件
const { state, dispatch } = useTodos(); // 获取 state 和 dispatch
return ( // 返回界面描述
<FlatList // 高效列表
data={state.todos} // 数据源
keyExtractor={(item) => item.id} // 从每项提取唯一 key
renderItem={({ item }) => ( // 如何渲染每一项
<View style={styles.itemRow}> // 每行容器
<Pressable // 可点击容器
style={{ flex: 1 }} // 占满剩余宽度
onPress={() => dispatch({ type: 'toggle', id: item.id })} // 点击切换完成状态
> // 开始 Pressable
<Text style={item.done ? styles.done : null}> // 文本,完成时应用删除线
{item.done ? '✓ ' : '○ '}{item.text} // 前缀符号 + 文本
</Text> // 结束 Text
</Pressable> // 结束 Pressable
<Pressable onPress={() => dispatch({ type: 'remove', id: item.id })}> // 删除按钮
<Text style={styles.delete}>删除</Text> // 删除文字
</Pressable> // 结束 Pressable
</View> // 结束容器
)} // 结束 renderItem
/> // 结束 FlatList
); // 结束 return
} // 结束 TodoList
function Summary() { // 定义汇总组件
const { state } = useTodos(); // 获取 state
const remaining = state.todos.filter((t) => !t.done).length; // 计算未完成数量
return <Text>还剩 {remaining} 项未完成</Text>; // 显示统计
} // 结束 Summary
export default function App() { // 定义并默认导出 App
return ( // 返回界面描述
<TodoProvider> // 使用 TodoProvider 包裹
<View style={styles.container}> // 外层容器
<Text style={styles.heading}>我的待办</Text> // 标题
<AddTodo /> // 添加组件
<Summary /> // 汇总组件
<TodoList /> // 列表组件
</View> // 结束容器
</TodoProvider> // 结束 Provider
); // 结束 return
} // 结束 App
const styles = StyleSheet.create({ // 创建样式表
container: { flex: 1, padding: 20, paddingTop: 60 }, // 容器样式
heading: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 }, // 标题样式
addRow: { flexDirection: 'row', marginBottom: 16 }, // 添加行样式
input: { // 输入框样式
flex: 1, // 占满剩余宽度
borderWidth: 1, // 边框宽度 1
borderColor: '#ccc', // 边框颜色
borderRadius: 6, // 圆角 6
paddingHorizontal: 10, // 水平内边距 10
height: 40, // 高度 40
marginRight: 8, // 右侧间距 8
}, // 结束 input 样式
itemRow: { // 列表行样式
flexDirection: 'row', // 水平排列
alignItems: 'center', // 垂直居中
justifyContent: 'space-between', // 两端对齐
paddingVertical: 12, // 垂直内边距 12
borderBottomWidth: 1, // 底部分隔线
borderBottomColor: '#eee', // 分隔线颜色
}, // 结束 itemRow 样式
done: { textDecorationLine: 'line-through', color: '#aaa' }, // 完成样式
delete: { color: '#d00' }, // 删除按钮样式
}); // 结束样式表
3.3 关键设计
TodoProvider封装了useReducer和Context.Provider;useTodos是自定义 Hook,封装useContext并做检查;AddTodo、TodoList、Summary都通过useTodos访问状态和 dispatch;- 任何组件想读写待办数据,只需调用
useTodos,不需要 props。
3.4 自定义 Hook 的价值
// JavaScript + React Native
function useTodos() { // 定义自定义 Hook
const context = useContext(TodoContext); // 读取 Context
if (!context) { // 如果没有 Provider
throw new Error('useTodos 必须在 TodoProvider 内使用'); // 抛出错误
} // 结束判断
return context; // 返回 context
} // 结束 useTodos
三个好处:使用更简洁;忘记包 Provider 时立刻报错;以后想换实现,只改这个 Hook。
这是 React 社区推荐的 Context 使用模式。
四、Context 的性能优化
4.1 问题:Provider 变化,所有消费者重渲染
// JavaScript + React Native (JSX)
<TodoContext.Provider value={{ state, dispatch }}> // value 是新对象
每次 TodoProvider 渲染,{ state, dispatch } 都是新对象,即使 state 没变。所有消费者都会重渲染。
4.2 优化一:拆分 Context
// JavaScript + React Native
const TodoStateContext = createContext(null); // 状态 Context
const TodoDispatchContext = createContext(null); // 操作 Context
function TodoProvider({ children }) { // 定义 Provider
const [state, dispatch] = useReducer(reducer, initialState); // 使用 useReducer
return ( // 返回界面描述
<TodoStateContext.Provider value={state}> // 提供状态
<TodoDispatchContext.Provider value={dispatch}> // 提供 dispatch
{children} // 渲染子组件
</TodoDispatchContext.Provider> // 结束 dispatch Provider
</TodoStateContext.Provider> // 结束 state Provider
); // 结束 return
} // 结束 TodoProvider
只关心 dispatch 的组件,不会因为 state 变化而重渲染。因为 dispatch 的引用是稳定的。
4.3 优化二:用 useMemo 稳定 value
// JavaScript + React Native
import { useMemo } from 'react'; // 引入 useMemo
function TodoProvider({ children }) { // 定义 Provider
const [state, dispatch] = useReducer(reducer, initialState); // 使用 useReducer
const value = useMemo(() => ({ state, dispatch }), [state]); // 只在 state 变化时创建新对象
return ( // 返回界面描述
<TodoContext.Provider value={value}> // Provider 提供稳定的 value
{children} // 渲染子组件
</TodoContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 TodoProvider
4.4 优化三:按需拆分 Provider
不要把所有状态都塞进一个大 Context。按功能拆分:主题、用户、购物车各一个 Provider,各自独立。
4.5 何时不该用 Context
Context 适合"变化不频繁、被很多组件读取"的数据:主题、语言、当前用户、全局配置。
如果数据变化非常频繁(比如动画、实时位置),Context 可能不是最佳选择。
五、Context 与"视图 = 函数(状态)"
Context 没有打破"视图 = 函数(状态)",只是换了一种方式传递状态:
- 以前:状态通过 props 一层一层往下传;
- 现在:状态通过 Context 直接提供给需要的组件。
// 概念表达
视图 = 函数(props, state, context) // Context 是第三种输入
Context 是第三种输入。它和 props、state 一样,参与决定视图。
六、状态管理方案的演进
| 方案 | 适用场景 |
|---|---|
| useState | 组件内部简单状态 |
| useReducer | 组件内部复杂状态 |
| Context + useReducer | 跨组件共享、中等规模应用 |
| Redux / Zustand / Jotai | 大型应用、复杂状态逻辑、需要中间件和调试工具 |
学习顺序也是这样:先掌握 useState 和 useReducer,再学 Context,最后按需选择第三方库。不要一上来就引入 Redux,大多数应用用 Context + useReducer 就够了。
七、练习题
练习题1:选择题——Context 的作用
题目:Context 主要解决什么问题?
A. 让组件渲染更快
B. 避免 props 层层透传
C. 替代 useState
D. 处理网络请求
参考答案:B
解读:Context 让后代组件直接读取共享数据,不需要中间组件转发 props。它不替代 useState,也不负责网络请求。
练习题2:填空题——创建 Context
题目:补全代码,创建一个默认值为 'light' 的 Context。
// JavaScript + React Native
import { createContext } from 'react'; // 引入 createContext
const ThemeContext = createContext(____); // 补全默认值
参考答案:'light'
解读:createContext 接收一个默认值。这个默认值只在没有 Provider 包裹时生效。
练习题3:改错题——useContext 用法
题目:下面代码有一处错误,请指出并修正。
// JavaScript + React Native
import { useContext } from 'react'; // 引入 useContext
function MyComponent() { // 定义组件
const theme = useContext(); // 错误:没有传入 Context
return <Text>{theme}</Text>; // 返回文本
} // 结束组件
参考答案:useContext 需要传入 Context 对象:const theme = useContext(ThemeContext);
解读:useContext 的参数是 createContext 创建的 Context 对象,不是空参数。
练习题4:代码阅读题——Context 传递
题目:阅读代码,说出 DeepChild 能否读到 value。
// JavaScript + React Native
const MyContext = createContext('default'); // 创建 Context
function DeepChild() { // 定义深层子组件
const value = useContext(MyContext); // 读取 Context
return <Text>{value}</Text>; // 返回文本
} // 结束 DeepChild
function App() { // 定义 App
return ( // 返回界面描述
<MyContext.Provider value="hello"> // Provider 提供 hello
<View> // 外层容器
<View> // 内层容器
<DeepChild /> // 深层子组件
</View> // 结束内层
</View> // 结束外层
</MyContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 App
参考答案:能读到,显示"hello"。
解读:Context 无视组件嵌套深度。只要 DeepChild 在 Provider 内部,就能读到 Provider 提供的值。
练习题5:动手题——简单 Context
题目:创建一个 LanguageContext,默认值为 'zh',在 App 中通过 Provider 提供 'en',并写一个组件读取它。
参考答案:
// JavaScript + React Native
import { createContext, useContext } from 'react'; // 引入需要的 Hook
import { Text } from 'react-native'; // 引入 Text
const LanguageContext = createContext('zh'); // 创建 Context,默认 'zh'
function LanguageDisplay() { // 定义显示组件
const lang = useContext(LanguageContext); // 读取语言
return <Text>当前语言:{lang}</Text>; // 显示语言
} // 结束 LanguageDisplay
export default function App() { // 定义并默认导出 App
return ( // 返回界面描述
<LanguageContext.Provider value="en"> // Provider 提供 en
<LanguageDisplay /> // 渲染显示组件
</LanguageContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 App
解读:Provider 的 value 会覆盖默认值。后代组件通过 useContext 读取到的是 'en'。
练习题6:简答题——reducer 为什么是纯函数
题目:为什么 reducer 必须是纯函数?
参考答案:因为 React 可能在不同时机调用 reducer,或在开发模式下重复调用。纯函数保证相同输入得到相同输出,不产生副作用,行为可预测。
解读:reducer 应该只根据 state 和 action 计算新的 state,不修改原 state,不发请求,不写日志。
练习题7:useReducer 填空题
题目:补全代码,使 dispatch({ type: 'increment' }) 让 count 加一。
// JavaScript + React Native
function reducer(state, action) { // 定义 reducer
switch (action.type) { // 根据 action 类型分发
case 'increment': // 加一
return { ____ }; // 补全返回语句
default: // 其他
return state; // 返回原状态
} // 结束 switch
} // 结束 reducer
参考答案:...state, count: state.count + 1
解读:用展开运算符复制原状态,再覆盖 count。这是不可变更新。
练习题8:action 携带数据题
题目:写一个 action,添加一条待办事项,内容为"学习"。
参考答案:
// JavaScript + React Native
dispatch({ type: 'add', text: '学习' }); // 发送带数据的 action
解读:action 除了 type,还可以携带任意数据。reducer 通过 action.text 读取。
练习题9:代码阅读题——reducer 输出
题目:初始状态是 { count: 0 }。依次执行 dispatch({ type: 'increment' }) 和 dispatch({ type: 'increment' }) 后,count 是多少?
参考答案:2。
解读:每次 increment 都返回 { ...state, count: state.count + 1 }。两次调用后,count 从 0 变成 1,再变成 2。
练习题10:Context + useReducer 结构题
题目:写出 Context + useReducer 的核心结构。
参考答案:
// JavaScript + React Native
const MyContext = createContext(null); // 创建 Context
function reducer(state, action) { // 定义 reducer
switch (action.type) { // 根据 action 类型分发
// ... // 处理各种 action
default: // 默认
return state; // 返回原状态
} // 结束 switch
} // 结束 reducer
function MyProvider({ children }) { // 定义 Provider
const [state, dispatch] = useReducer(reducer, initialState); // 使用 useReducer
return ( // 返回界面描述
<MyContext.Provider value={{ state, dispatch }}> // 提供 state 和 dispatch
{children} // 渲染子组件
</MyContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 MyProvider
解读:useReducer 负责状态和更新逻辑,Context.Provider 负责把 state 和 dispatch 提供给后代。
练习题11:自定义 Hook 题
题目:写一个 useMyContext,读取 MyContext,并在没有 Provider 时抛出错误。
参考答案:
// JavaScript + React Native
function useMyContext() { // 定义自定义 Hook
const context = useContext(MyContext); // 读取 Context
if (!context) { // 如果没有 Provider
throw new Error('useMyContext 必须在 MyProvider 内使用'); // 抛出错误
} // 结束判断
return context; // 返回 context
} // 结束 useMyContext
解读:自定义 Hook 让使用更简洁,并提供错误检查。这是社区推荐的 Context 使用模式。
练习题12:性能优化题
题目:Context 的 Provider value 每次都创建新对象,会导致什么问题?如何优化?
参考答案:会导致所有消费该 Context 的组件每次都重新渲染。可以用 useMemo 稳定 value,或者拆分 Context。
解读:value 是对象时,引用变化会触发消费者重渲染。useMemo 只在依赖变化时才创建新对象。
练习题13:拆分 Context 题
题目:把 { state, dispatch } 拆成两个 Context,有什么好处?
参考答案:只关心 dispatch 的组件不会因为 state 变化而重渲染。因为 dispatch 的引用是稳定的。
解读:拆分 Context 是常见的性能优化手段,把"读数据"和"改数据"分开。
练习题14:综合题——计数器 Context
题目:用 Context + useReducer 实现一个全局计数器,包含 increment、decrement、reset 三个操作。
参考答案:
// JavaScript + React Native
import { createContext, useContext, useReducer } from 'react'; // 引入需要的 Hook
import { View, Text, Button } from 'react-native'; // 引入需要的组件
const CounterContext = createContext(null); // 创建 Context
function reducer(state, action) { // 定义 reducer
switch (action.type) { // 根据 action 类型分发
case 'increment': // 加一
return { count: state.count + 1 }; // 返回新状态
case 'decrement': // 减一
return { count: state.count - 1 }; // 返回新状态
case 'reset': // 重置
return { count: 0 }; // 返回初始状态
default: // 未知 action
return state; // 返回原状态
} // 结束 switch
} // 结束 reducer
function CounterProvider({ children }) { // 定义 Provider
const [state, dispatch] = useReducer(reducer, { count: 0 }); // 使用 useReducer
return ( // 返回界面描述
<CounterContext.Provider value={{ state, dispatch }}> // 提供 state 和 dispatch
{children} // 渲染子组件
</CounterContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 CounterProvider
function useCounter() { // 定义自定义 Hook
return useContext(CounterContext); // 返回 Context
} // 结束 useCounter
function CounterDisplay() { // 定义显示组件
const { state } = useCounter(); // 获取 state
return <Text style={{ fontSize: 48 }}>{state.count}</Text>; // 显示计数
} // 结束 CounterDisplay
function CounterControls() { // 定义控制组件
const { dispatch } = useCounter(); // 获取 dispatch
return ( // 返回界面描述
<View> // 外层容器
<Button title="加一" onPress={() => dispatch({ type: 'increment' })} /> // 加一
<Button title="减一" onPress={() => dispatch({ type: 'decrement' })} /> // 减一
<Button title="重置" onPress={() => dispatch({ type: 'reset' })} /> // 重置
</View> // 结束容器
); // 结束 return
} // 结束 CounterControls
export default function App() { // 定义并默认导出 App
return ( // 返回界面描述
<CounterProvider> // 使用 Provider
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> // 居中容器
<CounterDisplay /> // 显示组件
<CounterControls /> // 控制组件
</View> // 结束容器
</CounterProvider> // 结束 Provider
); // 结束 return
} // 结束 App
解读:这是 Context + useReducer 的典型用法。状态和更新逻辑集中在 reducer 中,通过 Context 提供给所有需要的组件。
练习题15:综合应用题——全局主题与用户
题目:创建两个 Context:ThemeContext(主题)和 UserContext(用户)。App 中同时提供这两个 Context,并写一个组件同时读取它们。
参考答案:
// JavaScript + React Native
import { createContext, useContext, useState } from 'react'; // 引入需要的 Hook
import { View, Text, Button } from 'react-native'; // 引入需要的组件
const ThemeContext = createContext('light'); // 主题 Context
const UserContext = createContext(null); // 用户 Context
function Dashboard() { // 定义仪表盘组件
const theme = useContext(ThemeContext); // 读取主题
const user = useContext(UserContext); // 读取用户
return ( // 返回界面描述
<View // 外层容器
style={{ // 样式
flex: 1, // 占满
justifyContent: 'center', // 垂直居中
alignItems: 'center', // 水平居中
backgroundColor: theme === 'dark' ? '#222' : '#fff', // 背景色由主题决定
}} // 结束样式
> // 结束开始标签
<Text style={{ color: theme === 'dark' ? '#fff' : '#000' }}> // 文字颜色由主题决定
欢迎,{user ? user.name : '游客'} // 显示用户名或游客
</Text> // 结束 Text
</View> // 结束容器
); // 结束 return
} // 结束 Dashboard
export default function App() { // 定义并默认导出 App
const [theme, setTheme] = useState('light'); // 创建状态 theme
const [user, setUser] = useState({ name: '小明' }); // 创建状态 user
return ( // 返回界面描述
<ThemeContext.Provider value={theme}> // 提供主题
<UserContext.Provider value={user}> // 提供用户
<Dashboard /> // 渲染仪表盘
<Button // 切换主题按钮
title="切换主题" // 按钮文字
onPress={() => setTheme(theme === 'light' ? 'dark' : 'light')} // 点击切换
/> // 结束 Button
</UserContext.Provider> // 结束用户 Provider
</ThemeContext.Provider> // 结束主题 Provider
); // 结束 return
} // 结束 App
解读:多个 Context 可以嵌套使用。组件可以同时读取多个 Context,互不影响。按功能拆分 Context 是推荐做法。
八、总结
这一课我们学习了 React Native 中跨组件共享状态的方案:
- prop drilling 是深层组件树的痛点;
- Context 让后代组件直接读取共享数据;
createContext创建 Context,Provider提供数据,useContext读取数据;useReducer适合管理复杂状态,把更新逻辑集中到 reducer;- Context + useReducer 是轻量级状态管理方案;
- 自定义 Hook 封装
useContext,提供统一入口和错误检查; - Provider 的
value变化会导致所有消费者重渲染,可用useMemo或拆分 Context 优化; - Context 适合变化不频繁、被很多组件读取的数据;
- 状态管理方案按需选择:
useState→useReducer→ Context +useReducer→ 第三方库。
记住一句话:
Context 不是替代 props,而是给状态多开了一条通道。
第7课预告
下一课,我们进入 【React Native娓娓道来】第7课 表单与用户输入:TextInput、表单校验与键盘处理。
更多推荐



所有评论(0)