基于 React 的倒计时组件实现:暴露方法供父组件状态管理
·
基于 React 的倒计时组件实现:暴露方法供父组件状态管理
在 React 开发中,倒计时组件是常见的交互元素,常用于验证码倒计时、录制时长限制等场景。本文将实现一个支持父组件获取剩余时间的倒计时组件(CountDown),通过 forwardRef + useImperativeHandle 暴露核心方法,配合父组件完成超时判断与提示逻辑。
一、核心技术点说明
- forwardRef:用于将父组件传递的 ref 转发到子组件内部,实现父组件对於子组件的 DOM 或实例引用。
- useImperativeHandle:自定义暴露给父组件的实例值,避免暴露子组件内部所有 DOM 或状态,仅对外提供必要方法(本文为
getTime方法)。 - 自定义倒计时 Hook(useCountDown):封装倒计时核心逻辑,负责秒数递减、计时停止等功能,使组件职责更单一。
二、完整实现代码
1. 自定义倒计时 Hook(useCountDown.ts)
首先封装倒计时核心逻辑,返回当前剩余时间,简化组件内部代码:
import { useState, useEffect, useRef } from 'react';
/**
* 自定义倒计时 Hook
* @param initialTime 初始倒计时秒数
* @returns 剩余时间
*/
export const useCountDown = (initialTime: number) => {
const [time, setTime] = useState(initialTime);
const timerRef = useRef<NodeJS.Timeout | null>(null);
// 初始化倒计时
useEffect(() => {
// 清除之前的定时器,避免重复计时
if (timerRef.current) clearInterval(timerRef.current);
// 启动倒计时
timerRef.current = setInterval(() => {
setTime(prev => {
// 倒计时结束,清除定时器
if (prev <= 1) {
clearInterval(timerRef.current!);
return 0;
}
return prev - 1;
});
}, 1000);
// 组件卸载时清除定时器(避免内存泄漏)
return () => {
if (timerRef.current) clearInterval(timerRef.current);
};
}, [initialTime]);
return { time };
};
2. 倒计时组件(CountDown.tsx)
使用 forwardRef 转发 ref,通过 useImperativeHandle 暴露 getTime 方法,供父组件获取剩余时间:
import { forwardRef, useImperativeHandle } from 'react';
import { useCountDown } from './useCountDown';
// 定义组件暴露给父组件的方法类型
interface CountDownExposedMethods {
getTime: () => number; // 获取剩余时间的方法
}
// 通过 forwardRef 转发父组件传递的 ref
const CountDown = forwardRef<CountDownExposedMethods>((_, ref) => {
const { time } = useCountDown(60); // 初始化 60 秒倒计时
// 自定义暴露给父组件的方法,依赖 time 状态更新
useImperativeHandle(ref, () => ({
getTime: () => time, // 返回当前剩余时间
}), [time]); // 当 time 变化时,更新暴露的方法(确保获取最新值)
// 渲染倒计时样式(补零处理,确保格式统一:01s、02s...60s)
return (
<div className="count-down text-blue-600 font-medium">
{time.toString().padStart(2, '0')} s
</div>
);
});
// 设置组件显示名称(便于调试)
CountDown.displayName = 'CountDown';
export default CountDown;
3. 父组件(ParentComponent.tsx)
通过 ref 获取子组件暴露的 getTime 方法,实时获取剩余时间,判断是否超时并显示提示:
import { useRef, useState, useEffect } from 'react';
import CountDown from './CountDown';
const ParentComponent = () => {
// 创建 ref 用于关联倒计时组件
const countDownRef = useRef<{ getTime: () => number }>(null);
// 超时状态(控制提示显示)
const [isTimeout, setIsTimeout] = useState(false);
// 监听倒计时状态,判断是否超时
useEffect(() => {
// 每 100ms 检查一次剩余时间(避免遗漏超时瞬间)
const checkTimer = setInterval(() => {
// 确保 ref 已关联组件且存在 getTime 方法
if (countDownRef.current) {
const remainingTime = countDownRef.current.getTime();
// 剩余时间为 0 时,标记为超时
if (remainingTime === 0) {
setIsTimeout(true);
clearInterval(checkTimer); // 超时后停止检查
}
}
}, 100);
// 组件卸载时清除检查定时器
return () => {
clearInterval(checkTimer);
};
}, []);
// 重置倒计时(可选功能,如需重新开始倒计时)
const resetCountDown = () => {
setIsTimeout(false);
// 这里可结合实际需求扩展(如重新渲染组件重置倒计时)
// 若需更灵活的重置,可在 CountDown 组件中暴露 reset 方法
};
return (
<div className="parent-container p-8 max-w-md mx-auto">
<h3 className="text-xl font-bold mb-4">录制时长限制</h3>
{/* 倒计时组件,传递 ref */}
<CountDown ref={countDownRef} />
{/* 超时提示(超时后显示) */}
{isTimeout && (
<div className="mt-4 text-red-500 text-sm">
已超时,请重新录制
</div>
)}
{/* 重置按钮(超时后显示) */}
{isTimeout && (
<button
onClick={resetCountDown}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
重新开始
</button>
)}
</div>
);
};
export default ParentComponent;
三、关键逻辑解析
-
方法暴露逻辑:
- 子组件通过
forwardRef接收父组件传递的 ref,再通过useImperativeHandle自定义暴露的内容,仅对外提供getTime方法,隐藏内部的time状态和定时器逻辑,符合“最小暴露原则”。 useImperativeHandle的依赖数组包含time,确保每次time更新时,父组件通过getTime获取的都是最新剩余时间。
- 子组件通过
-
超时判断逻辑:
- 父组件通过
useRef创建 ref 并关联子组件,在useEffect中启动定时器,定期调用子组件的getTime方法获取剩余时间。 - 当剩余时间为 0 时,设置
isTimeout为true,显示超时提示,同时清除检查定时器避免无效循环。
- 父组件通过
-
性能与内存优化:
- 子组件中使用
useRef存储定时器实例,在组件卸载和倒计时结束时及时清除,避免内存泄漏。 - 父组件的检查定时器在组件卸载时清除,同样避免内存泄漏。
- 子组件中使用
四、扩展场景
- 自定义初始倒计时:可将
useCountDown的初始时间改为 props 传递,使组件支持动态配置(如props.initialTime)。 - 暂停/继续功能:在
useCountDown中扩展pause和resume方法,通过useImperativeHandle暴露给父组件,实现倒计时的暂停与继续。 - 格式自定义:支持父组件传递格式函数(如
formatTime: (time: number) => string),自定义倒计时显示格式(如mm:ss)。
五、总结
本文通过 forwardRef + useImperativeHandle 实现了父子组件的方法通信,使父组件能够灵活获取倒计时组件的剩余时间并进行超时处理。这种方式既保证了子组件的封装性,又满足了父组件的状态管理需求,适用于各类需要父子组件协作的倒计时场景。同时,通过自定义 Hook 封装核心逻辑,使代码结构更清晰、可维护性更强。
更多推荐



所有评论(0)