文章目录
前言
在 React 应用中,有时我们需要在组件树的不同层次之间共享状态或配置。传统的做法是通过 props 一层层地传递下去,但这会导致组件变得臃肿并且难以维护。为了解决这个问题,React 提供了 Context API,它允许我们在组件树中无痛地传递数据。
一、Context API 的基本概念
Context 对象
React.createContext() 方法用于创建一个 Context 对象,该对象是 React 提供的一个全局容器,用于保存需要传递的数据。
Provider 组件
Context.Provider 是一个 React 组件,它接受一个 value 属性,该属性会被传递给子组件中的 Consumer 或 useContext。
Consumer 组件
Context.Consumer 是一个 React 组件,它接受一个函数作为子组件,并将 Context 的当前值作为参数传递给该函数。
useContext Hook
useContext 是一个 Hook,它允许函数组件订阅 Context 的变化。。
二、创建 Context
要使用 Context API,首先需要创建一个 Context 对象。我下面是写一个音频播放器按钮的例子,使用函数组件、ts
'use client';
import React, {
useState,
useEffect,
useRef,
createContext,
useCallback,
FC,
} from 'react';
import { AudioContextValue } from '../type';
// 提供一个空函数作为默认值,实际使用中会被替换
const defaultValue: AudioContextValue = {
isPlaying: false,
play: () => {},
pause: () => {},
};
// 创建 AudioContext,并传入默认值
export const AudioContext = createContext<AudioContextValue>(defaultValue);
三、使用 Provider 分发值
Context 对象和Provider可以分开写,我现在是写在一个文件了,引用都在上面。
最主要的内容在return这里
- 使用 <AudioContext.Provider> 组件将 isPlaying、play 和 pause 作为值传递给子组件。
- {children} 表示 Provider 组件的子元素,这些子元素将能够访问传递的值。
const Provider: FC<AudioContextProviderProps> = ({ children }) => {
const [isPlaying, setIsPlaying] = useState(false); // 播放状态
const audioRef = useRef<HTMLAudioElement | null>(null);
// 初始化音频
useEffect(() => {
audioRef.current = new Audio('/music1.mp3');
// 播放结束后重新播放
const playOnEnded = useCallback(() => {
if (audioRef.current) {
audioRef.current.currentTime = 0;
audioRef.current.play();
}
}, []);
audioRef.current.addEventListener('ended', playOnEnded);
return () => {
if (audioRef.current) {
audioRef.current.removeEventListener('ended', playOnEnded);
audioRef.current.pause();
audioRef.current.currentTime = 0;
audioRef.current = null;
}
};
}, []);
// 播放
const play = useCallback(() => {
if (audioRef.current) {
audioRef.current.play();
setIsPlaying(true);
}
}, []);
// 暂停
const pause = useCallback(() => {
if (audioRef.current) {
audioRef.current.pause();
setIsPlaying(false);
}
}, []);
return (
<AudioContext.Provider value={{ isPlaying, play, pause }}>
{children}
</AudioContext.Provider>
);
};
export const AudioContextProvider = Provider;
四、使用 Consumer 消费值
虽然现在 useContext 钩子是访问 Context 的推荐方法,但 Context.Consumer 仍然是有效的,并且在一些情况下非常有用,比如在类组件中使用 Context。
创建 Provider
// 创建一个 Context Provider 组件
const MyContextProvider: React.FC<MyContextProviderProps> = ({ children }) => {
const value = 'Hello from Context!';
return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
};
export {MyContextProvider };
使用
import React from 'react';
import { MyContext } from './MyContextProvider';
const MyComponent = () => {
return (
<MyContext.Consumer>
{value => (
<div>
<p>Context Value: {value}</p>
</div>
)}
</MyContext.Consumer>
);
};
export default MyComponent;
五、使用 useContext Hook
'use client';
import React, { useState, useRef, useCallback, useContext } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { Line } from '@react-three/drei';
import { AudioContext } from '../lib/contexts/AudioContext'; //引入Context
// 绘制动态曲线的组件省略
const BackgroundMusicButton = () => {
const { isPlaying, play, pause } = useContext(AudioContext);
// 从 useContext(AudioContext) 返回的值中解构出 isPlaying、play 和 pause。
// 这些值是由 AudioContext 提供的。
// 使用useCallback缓存函数
const togglePlay = useCallback(() => {
if (isPlaying) {
pause();
} else {
play();
}
}, [isPlaying]);
return (
<div className="bg-button">
<button onClick={togglePlay}>
<Canvas frameloop="demand">
{/* <Perf /> */}
{/* 性能检测插件 */}
{isPlaying ? (
<DynamicCurve />
) : (
<Line
points={[
[-2, 0, 0],
[2, 0, 0],
]} // 定义直线的起点和终点
color="#fff" // 线条颜色
lineWidth={2} // 线条宽度
/>
)}
{/* 直线---暂停状态 */}
</Canvas>
</button>
</div>
);
};
export default BackgroundMusicButton;