React上下文之useContext

useContext 使用场景

. 组件的嵌套层级很深,当父组件想把数据共享给最深层的子组件时向组件树深层传递数据

. 图示:

useContext 语法格式

tsx 复制代码
import React, { useContext } from 'react'

const [theme, setTheme] = useState(null);
//1. 在全局创建 Context 对象
const ThemeContext = React.createContext(theme)

//2. 在父组件中使用 Context 提供数据
const App = () => {
  return <ThemeContext value={theme}>    ...
  </ThemeContext>
}

//3. 在子组件中使用 useContext 使用数据
const Son = () => {
  const myCtx = useContext(ThemeContext)
  return <div></div>
}

Context 基础用法

  • 在父组件中,调用 React.createContext 向下共享数据;在子组件中调用 useContext() 获取数据。
tsx 复制代码
import React, { useState, useContext } from 'react'

// 声明 TS 类型
type ContextType = { count: number; setCount: React.Dispatch<React.SetStateAction<number>> }

// 1. 创建 Context 对象
const AppContext = React.createContext<ContextType>({} as ContextType)

export const LevelA: React.FC = () => {
  const [count, setCount] = useState(0)

  return (
    <div style={{ padding: 30, backgroundColor: 'lightblue', width: '50vw' }}>
      <p>count:{count}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>+1</button>

      <AppContext.Provider value={{ count, setCount }}>
        <LevelB />
      </AppContext.Provider>
    </div>
  )
}

export const LevelB: React.FC = () => {
  return (
    <div style={{ padding: 30, backgroundColor: 'lightgreen' }}>
      <LevelC />
    </div>
  )
}

export const LevelC: React.FC = () => {
  const ctx = useContext(AppContext)

  return (
    <div style={{ padding: 30, backgroundColor: 'lightsalmon' }}>
      <p>count值是:{ctx.count}</p>
      <button onClick={() => ctx.setCount((prev) => prev + 1)}>+1</button>
      <button onClick={() => ctx.setCount(0)}>重置</button>
    </div>
  )
}

非侵入式 Context

  • 把 Context 封装到独立的 Wrapper 函数式组件中- 非侵入式 Context 不会影响组件的结构,只会在组件树中创建一个共享的 Context 对象。
tsx 复制代码
// 声明 TS 类型
type ContextType = { count: number; setCount: React.Dispatch<React.SetStateAction<number>> }
// 创建 Context 对象
const AppContext = React.createContext<ContextType>({} as ContextType)

// 定义独立的 Wrapper 组件,被 Wrapper 嵌套的子组件会被 Provider 注入数据
export const AppContextWrapper: React.FC<React.PropsWithChildren> = (props) => {
  // 1. 定义要共享的数据
  const [count, setCount] = useState(0)
  // 2. 使用 AppContext.Provider 向下共享数据
  return <AppContext value={{ count, setCount }}>{props.children}</AppContext>
}
  • 在 App.tsx 中使用 AppContextWrapper 包裹
tsx 复制代码
import React from 'react'
import { AppContextWrapper } from '@/xxx.tsx'

const App: React.FC = () => {
  return (
    <AppContextWrapper>
      <!-- AppContextWrapper 中嵌套使用了 LevelA 组件,形成了父子关系 -->
      <!-- LevelA 组件会被当做 children 渲染到 Wrapper 预留的插槽中 -->
      <LevelA />
    </AppContextWrapper>
  )
}

export default App
  • LevelC 组件中使用 useContext 获取数据
tsx 复制代码
export const LevelC: React.FC = () => {
  // 使用 useContext 接收数据
  const ctx = useContext(AppContext)

  return (
    <div style={{ padding: 30, backgroundColor: 'lightsalmon' }}>
      <p>count值是:{ctx.count}</p>
      <button onClick={() => ctx.setCount((prev) => prev + 1)}>+1</button>
      <button onClick={() => ctx.setCount(0)}>重置</button>
    </div>
  )
}

进阶 useContext 结合 useReducer 使用场景

  • 定义 reducer 函数
tsx 复制代码
function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

const initialTasks = [
  { id: 0, text: 'Philosopher's Path', done: true },
  { id: 1, text: 'Visit the temple', done: false },
  { id: 2, text: 'Drink matcha', done: false }
];
  • 在根组件中创建 Context 对象 并抽离成独立组件
tsx 复制代码
import { createContext, useContext, useReducer } from 'react';

export const TasksContext = createContext(null);

export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  return (
    <TasksContext value={{ tasks , dispatch }}>
        {children}
    </TasksContext>
  );
}
  • 在根组件中使用 TasksProvider 包裹子组件
tsx 复制代码
export default function TaskApp() {
  return (
    <TasksProvider>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}
  • 在子组件中使用 useContext 获取数据
tsx 复制代码
import { useState, useContext } from 'react';
import { TasksContext } from './TasksContext.js';

export default function AddTask() {
  const [text, setText] = useState('');
  const { dispatch } = useContext(TasksContext);

  function handleAdd() {
    setText('');
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    }); 
  }
  return (
    <>
      <input
        placeholder="Add task"
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={() => handleAdd()}>Add</button>
    </>
  );
}
相关推荐
tager4 小时前
🔥3行代码搞定全局代理!告别插件依赖的极简方案
前端·fiddler·charles
gnip5 小时前
axios 拦截器实现用户无感刷新 access_token
前端
程序员码歌5 小时前
【零代码AI编程实战】AI灯塔导航-成果展示篇
前端·ai编程·cursor
gnip6 小时前
前端实现即时通讯,常用的技术
前端
烛阴6 小时前
告别 any!用联合类型打造更灵活、更安全的 TS 代码
前端·typescript
excel7 小时前
全面解析 JavaScript 类继承:方式、优缺点与应用场景
前端
用户21411832636027 小时前
dify案例分享-100% 识别率!发票、汇票、信用证全搞定的通用票据识别工作流
前端
拾光拾趣录8 小时前
基础 | HTML语义、CSS3新特性、浏览器存储、this、防抖节流、重绘回流、date排序、calc
前端·面试
小小小小宇9 小时前
前端监测用户卡顿之INP
前端
小小小小宇9 小时前
监测用户在浏览界面过程中的卡顿
前端