为什么 Safari 显示“Link Not Found”(而 Chrome 没有)
Source: Dev.to
请提供您希望翻译的具体文本内容,我将为您翻译成简体中文。
Problem
我们构建的 URL 缩短服务在 Safari(iOS 和 macOS)上显示 “Link Not Found” 错误,而 Chrome、Firefox 和 Edge 则正常工作。iPhone 用户点击短链接时,有时会在目标页面加载之前短暂闪现错误页面。此问题表现为间歇性,难以复现,并且在 Chrome 上从未出现。
调查
我们添加了轻量级调试仪器:一个 useRef 数组记录每个函数调用的时间戳,并持久化到 localStorage,使日志在导航后仍然保留。
示例调试输出来自用户设备:
[0] { event: "handleRedirect called", timestamp: 1709683200001 }
[1] { event: "API success, redirecting", timestamp: 1709683200250 }
[2] { event: "handleRedirect called", timestamp: 1709683200252 }
[3] { event: "API error: request cancelled", timestamp: 1709683200260 }
条目 [2] 显示 handleRedirect 被调用了 两次,第二次调用在第一次成功重定向后仅 2 ms。
组件的行为
- 挂载 →
useEffect调用了handleRedirect() handleRedirect()通过 API 解析目标 URL- 成功后,
safeRedirect(targetUrl)设置window.location.href - 导航后,状态被更新(
markVisitedInSession),将hasVisitedInSession从false改为true handleRedirect被useCallback包裹,hasVisitedInSession作为依赖项
当状态改变时,React 创建了一个 新的 handleRedirect 回调引用。useEffect 检测到依赖项变化并重新运行 effect,再次调用 handleRedirect——即使页面已经在导航中。
根本原因:Safari 的导航行为
- Chrome:设置
window.location.href会立即停止当前页面的 JavaScript 执行。第二个handleRedirect永远不会运行。 - Safari:JavaScript 在导航进行期间仍会继续运行。第二次调用被触发,启动 API 请求,但由于页面正处于导航中,该请求被取消。catch 块渲染错误页面,产生“未找到链接”的闪现。
修复
我们引入了一个使用 useRef(而不是 useState)的 guard,以确保重定向逻辑只运行一次。
// Guard to prevent double redirects
const hasRedirected = useRef(false);
const handleRedirect = useCallback(async (pwd?: string) => {
// If we already redirected and this isn’t a password retry, bail out
if (hasRedirected.current && !pwd) return;
// …resolve URL, handle previews, etc…
// Mark that we have redirected before navigating
hasRedirected.current = true;
safeRedirect(targetUrl, '/');
}, [/* other deps */]);
为什么 useRef 有效
- No re‑render: Updating a ref does not trigger a component render, so the callback identity stays the same.
- Persistence across renders: The ref value survives across renders, silently blocking subsequent calls.
If we had used useState for the guard, setting it to true would cause another render, potentially creating a new callback identity and re‑triggering the effect—a loop we wanted to avoid.
后续
- 已在 Safari、Chrome 和 Firefox 上验证了修复。
- 移除了调试仪器(localStorage 日志记录和调试面板)。
- 保留了
useRef保护作为针对该 Safari 特定行为的三行防御。
要点
- 浏览器导航行为不同:Chrome 在
window.location.href时会暂停 JavaScript;Safari 则不会。这不是任一浏览器的 bug——规范并未规定何时必须停止执行。 - React Hook 可能产生隐藏循环:当
useCallback与useEffect的依赖包含在回调内部更新的状态时,可能导致不可见的重新执行,尤其在导航介入时。 - 在需要时对生产环境进行监控:当本地无法重现 bug 时,可通过标志位发布最小化的监控代码,让真实设备揭示真相。
- 倾向使用
useRef保存非渲染状态:当需要可变状态且不应触发渲染(例如“已重定向”标记)时,useRef是合适的工具。
你是否曾被 Safari 特有的 JavaScript 行为坑到?在评论区分享你的故事吧。
由 jo4.io 构建 — 一个在所有浏览器(包括 Safari)上都能使用的 URL 缩短服务。