你有没有在手机上刷微博、刷朋友圈、刷小红书的时候,手指从上往下拉一下,页面就自动刷新了?这就是我们常说的------下拉刷新功能。
今天,我们就来揭开它的神秘面纱,看看它是怎么用代码实现的。
一、下拉刷新到底是什么?
想象一下:你正在看一个笔记列表,突然想看看有没有新内容。于是你用手指从屏幕顶部往下拉,这时页面会:
- 向下移动(像被你拽下来一样)
- 显示"下拉刷新..." → "释放刷新..." → "加载中..."
- 松手后,重新加载新数据
- 加载完自动弹回去
这个过程,就是"下拉刷新"。
我们要做的,就是用代码模拟这个交互行为。
二、核心思路:四个关键步骤
整个下拉刷新组件的实现,可以分成 4 个步骤:
- 👉 监听用户的手指动作(触摸开始、移动、结束)
- 📊 管理状态(比如当前拉了多少距离)
- 🎨 实现动画效果(下拉和回弹)
- 🔗 和父组件通信(告诉它"该刷新数据啦!")
下面我们一个一个来看。
三、第一步:监听用户的手指操作
我们要知道用户什么时候开始拉、拉了多少、什么时候松手。这就要靠触摸事件。
1. onTouchStart
:记录起始位置
js
const onTouchStart = (e) => {
const start_y = e.touches[0].clientY
setStartY(start_y)
}
👉 解释:当用户手指刚碰到屏幕时,记录下触摸的Y坐标(比如是屏幕第500像素的位置),保存起来。
2. onTouchMove
:计算下拉距离
js
const onTouchMove = (e) => {
const move_y = e.touches[0].clientY
if (move_y < startY) return // 防止向上滑动也触发
setDistance(move_y - startY) // 当前拉了多少距离
setTranslateY(distance ** 0.8) // 控制页面下移多少(更自然)
if (distance >= 100) {
setCurrent('释放刷新...')
}
}
👉 解释:
- 用户一边拉,我们就一边计算他拉了多少(
move_y - startY
)。 - 我们不让页面跟着拉多少就下移多少,而是用了个"指数函数 "(
distance ** 0.8
),这样越拉越慢,手感更顺滑。 - 当拉到 100px 以上,就提示用户:"可以松手啦!"
3. onTouchEnd
:松手后判断是否刷新
js
const onTouchEnd = () => {
if (distance >= 100) {
setCurrent('加载中...')
// 开始回弹动画
timer = setInterval(() => {
setTranslateY(prev => prev - 5)
}, 20)
onLoad() // 调用父组件的加载函数
}
}
👉 解释:
- 用户松手时,如果拉的距离够长(≥100px),就认为"我要刷新"。
- 显示"加载中..."
- 启动一个定时器,每20毫秒让页面往上移5px,实现"慢慢弹回去"的动画。
- 同时调用
onLoad()
,告诉父组件:"快去加载新数据吧!"
四、第二步:用状态管理记住关键数据
React 中用 useState
来保存各种状态:
js
const [startY, setStartY] = useState(0) // 起始Y坐标
const [distance, setDistance] = useState(0) // 当前拉了多少
const [translateY, setTranslateY] = useState(0) // 页面下移多少
const [current, setCurrent] = useState('下拉刷新...') // 状态文字
这些变量就像"记忆",让程序知道现在处于什么阶段。
✅ 小知识:
useState
是 React 的"状态钩子",可以让函数组件记住数据。
五、第三步:让页面动起来------动画效果
我们通过 CSS 的 transform: translateY()
来控制页面下移:
jsx
<div style={{ transform: `translateY(${translateY}px)` }}>
{children}
</div>
translateY
越大,页面就越往下"飘"。- 松手后,我们用
setInterval
每次减5px,直到回到顶部,实现平滑回弹。
还有一个细节:当页面快回到顶部时(比如 ≤40px),就清除定时器,防止无限执行:
js
useEffect(() => {
if (translateY <= 40) {
clearInterval(timer)
}
}, [translateY])
✅ 这叫"副作用监听",
useEffect
会自动检测translateY
变化。
六、第四步:和外面的世界沟通------组件通信
我们的下拉组件是"通用"的,它不知道该加载什么数据。所以需要父组件告诉它:
jsx
<Pull onLoad={onLoad} finished={finished} setFinished={setFinished}>
<section>你的内容</section>
</Pull>
onLoad
:松手后要执行的函数(比如重新请求接口)finished
:加载是否完成setFinished
:用来重置状态
当数据加载完成后,父组件设置 setFinished(true)
,下拉组件收到后就会自动回到顶部:
js
useEffect(() => {
if (finished) {
setTranslateY(0)
setFinished(false) // 重置,准备下次刷新
}
}, [finished])
✅ 这就是"父子组件通信",通过 props 传递函数和状态。
七、完整流程图解
scss
用户下拉
↓
onTouchStart 记录起点
↓
onTouchMove 计算距离,更新UI
↓
松手时 onTouchEnd 判断是否够长
↓
够长?→ 显示"加载中" + 启动回弹动画 + 调用 onLoad()
↓
onLoad() 请求新数据
↓
数据加载完,父组件 setFinished(true)
↓
Pull 组件收到,回到顶部,重置状态