自定义 Hooks 示例
演示 src/common/hook 下的 4 个 Hook:触底加载、跨域存储、验证码、安全重定向。
useReachBottom 触底加载
向下滚动到底部自动加载下一页(模拟分页)。
共 20 条 · lastId = 20
useCrosStorage 跨域存储
跨域/同域下统一读写;未配置 STORAGE_PROXY 时回退到 localStorage/sessionStorage。
结果:—
useCaptcha 验证码
点击图片刷新验证码;需配置 NEXT_PUBLIC_CAPTCHA_URL 才能加载图片。
点击图片刷新验证码
useNavigating 安全重定向
基于 useLocale 拼出带正确语言前缀的重定向地址。
自定义 Hooks(src/common/hook)使用说明
概述
src/common/hook/ 目录封装了 4 个通用 React Hook,覆盖分页加载、跨域存储、验证码、重定向四类场景:
| Hook | 文件 | 用途 |
|---|---|---|
useReachBottom | ReachBottomHook.tsx | 滚动触底自动加载下一页(无限滚动 / 分页) |
useCrosStorage | CrosStorageHook.tsx | 跨域(iframe postMessage)/ 同域统一读写存储 |
useCaptcha | CaptchaHook.tsx | 验证码图片加载与点击刷新 |
useNavigating | NavigatingHook.tsx | 带 locale 前缀的安全重定向 |
四个 Hook 职责单一,且都对 SSR(typeof window === "undefined")做了防御。
useReachBottom —— 触底加载
监听滚动容器的 scroll 事件,滚动到距底部 20px 内时调用 reachBottomHandler(lastId) 拉取下一页,并用 globalLoading(ref)防抖避免重复请求。
import useReachBottom from "@/common/hook/ReachBottomHook";
const { loading, lastId, containerRef } = useReachBottom(
async (lastId) => {
const page = await fetchNextPage(lastId); // 返回 Promise
return page.newLastId; // resolve 出新的游标
},
0 // initialLastId
);
return <div ref={containerRef} className="h-96 overflow-y-auto">{/* 列表 */}</div>;
参数
| 参数 | 类型 | 说明 |
|---|---|---|
| reachBottomHandler | (lastId: any) => Promise<any> | 触底回调,resolve 出新的 lastId |
| initialLastId | any | 初始游标 |
返回值
| 字段 | 类型 | 说明 |
|---|---|---|
| loading | boolean | 是否加载中 |
| lastId | any | 当前游标 |
| setLastId | (id) => void | 手动更新游标 |
| startLoading | () => void | 手动标记加载中 |
| finishLoading | (newLastId) => void | 结束加载并更新游标 |
| containerRef | RefObject<HTMLDivElement> | 挂到滚动容器上 |
注意
- 触发阈值 20px;请求完成后
globalLoading延迟 100ms 复位,避免滚动事件短时间重复触发。 - 内部
useEffect依赖[loading]:每次请求完成、loading翻转时重新绑定 scroll 监听,从而拿到最新的lastId,避免闭包缓存旧值。
useCrosStorage —— 跨域存储
跨域场景下通过隐藏 iframe + postMessage 与代理页通信;同域(或未配置 STORAGE_PROXY)时直接回退到 localStorage / sessionStorage。常用于多子域共享登录 token。
import useCrosStorage from "@/common/hook/CrosStorageHook";
const crosStorage = useCrosStorage(); // CrosStorage | undefined(首帧为 undefined)
await crosStorage?.set("token-value", "my_token"); // 写入
const v = await crosStorage?.get("my_token"); // 读取
await crosStorage?.remove("my_token"); // 删除
返回值
返回 CrosStorage | undefined,实例提供以下方法:
| 方法 | 说明 |
|---|---|
| set(value, key?, storage?) | 写入;key 默认 TOKEN_KEY,storage 默认 AUTOMATIC |
| get(key?, storage?) | 读取,resolve 出字符串或 null |
| remove(key?, storage?) | 删除,resolve 出被删除的值 |
| getToken / setToken / removeToken | 针对 token 的快捷方法 |
| locateToken(token?) | 定位 / 本地化当前登录用户 |
| destroy() | 移除 iframe(Hook 卸载时会自动调用) |
storage 取值:LOCAL / SESSION / AUTOMATIC;AUTOMATIC 由环境变量 NEXT_PUBLIC_TOKEN_STORAGE 决定。
注意:首帧返回 undefined(useEffect 里才 setCrosStorage),使用前需判空。
useCaptcha —— 验证码
返回一个 ref,把它挂到 <img> 上即可:挂载后自动加载验证码,点击图片刷新。加载成功后会把图片 visibility 设为 visible。
import useCaptcha from "@/common/hook/CaptchaHook";
const captchaRef = useCaptcha();
return <img ref={captchaRef} alt="captcha" />;
返回值:RefObject<HTMLImageElement>。
注意:依赖环境变量 NEXT_PUBLIC_CAPTCHA_URL;未配置时图片无法加载。
useNavigating —— 安全重定向
基于 useLocale() 拼出带正确语言前缀的跳转地址,避免手写 URL 导致 locale 丢失。
import useNavigating from "@/common/hook/NavigatingHook";
const Navigate = useNavigating();
// 或按需解构静态方法
const { redirectToLogin } = useNavigating();
Navigate.redirectToIndex(); // 2s 后跳转首页
Navigate.redirectTo("/some/path"); // 立即跳转指定地址
Navigate.redirectToLogin(true, 2000); // 2s 后跳转登录(可带 ref)
静态方法
| 方法 | 说明 |
|---|---|
| redirectToIndex(timeout=2000) | 延迟跳转到 WWW_ROOT/{locale}/ |
| redirectTo(directUrl) | 立即跳转到指定 URL;为空则走 redirectToIndex |
| redirectToLogin(withRef=true, timeout=2000) | 延迟跳转到 PASSPORT_ROOT/{locale}{LOGIN_URL};withRef 为 true 时带上当前 URL 作为 ref |
依赖环境变量:NEXT_PUBLIC_WWW_ROOT、NEXT_PUBLIC_PASSPORT_ROOT、NEXT_PUBLIC_LOGIN_URL。