Content #
虽然 React 组件基本上不需要关心太多的浏览器 API,但是有时候却是必须的:
- 界面需要根据在窗口大小变化重新布局;
- 在页面滚动时,需要根据滚动条位置,来决定是否显示一个“返回顶部”的按钮。
这都需要用到浏览器的 API 来监听这些状态的变化。那么我们就以滚动条位置的场景为例,来看看应该如何用 Hooks 优雅地监听浏览器状态。
正如 Hooks 的字面意思是“钩子”,它带来的一大好处就是:可以让 React 的组件绑定在任何可能的数据源上。这样当数据源发生变化时,组件能够自动刷新。把这个好处对应到滚动条位置这个场景就是:组件需要绑定到当前滚动条的位置数据上。
虽然这个逻辑在函数组件中可以直接实现,但是把这个逻辑实现为一个独立的 Hooks,既可以达到逻辑重用,在语义上也更加清晰。这个和上面的 useAsync 的作用是非常类似的。
我们可以直接来看这个 Hooks 应该如何实现:
import { useState, useEffect } from 'react';
// 获取横向,纵向滚动条位置
const getPosition = () => {
return {
x: document.body.scrollLeft,
y: document.body.scrollTop,
};
};
const useScroll = () => {
// 定一个 position 这个 state 保存滚动条位置
const [position, setPosition] = useState(getPosition());
useEffect(() => {
const handler = () => {
setPosition(getPosition(document));
};
// 监听 scroll 事件,更新滚动条位置
document.addEventListener("scroll", handler);
return () => {
// 组件销毁时,取消事件监听
document.removeEventListener("scroll", handler);
};
}, []);
return position;
};
有了这个 Hook,你就可以非常方便地监听当前浏览器窗口的滚动条位置了。比如下面的代码就展示了“返回顶部”这样一个功能的实现:
import React, { useCallback } from 'react';
import useScroll from './useScroll';
function ScrollTop() {
const { y } = useScroll();
const goTop = useCallback(() => {
document.body.scrollTop = 0;
}, []);
const style = {
position: "fixed",
right: "10px",
bottom: "10px",
};
// 当滚动条位置纵向超过 300 时,显示返回顶部按钮
if (y > 300) {
return (
<button onClick={goTop} style={style}>
Back to Top
</button>
);
}
// 否则不 render 任何 UI
return null;
}
通过这个例子,我们看到了如何将浏览器状态变成可被 React 组件绑定的数据源,从而在使用上更加便捷和直观。当然,除了窗口大小、滚动条位置这些状态,还有其它一些数据也可以这样操作,比如 cookies,localStorage, URL,等等。你都可以通过这样的方法来实现。