开篇:导航带来的新问题

第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 中跨组件共享状态的方案:

  1. prop drilling 是深层组件树的痛点;
  2. Context 让后代组件直接读取共享数据;
  3. createContext 创建 Context,Provider 提供数据,useContext 读取数据;
  4. useReducer 适合管理复杂状态,把更新逻辑集中到 reducer;
  5. Context + useReducer 是轻量级状态管理方案;
  6. 自定义 Hook 封装 useContext,提供统一入口和错误检查;
  7. Provider 的 value 变化会导致所有消费者重渲染,可用 useMemo 或拆分 Context 优化;
  8. Context 适合变化不频繁、被很多组件读取的数据;
  9. 状态管理方案按需选择:useState → useReducer → Context + useReducer → 第三方库。

记住一句话:

Context 不是替代 props,而是给状态多开了一条通道。


第7课预告

下一课,我们进入 【React Native娓娓道来】第7课 表单与用户输入:TextInput、表单校验与键盘处理。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐