---
id: navigation-blocking
track: learn
product: router
locale: zh-CN
order: 12
revision: 2
sourceCheckedOn: "2026-08-03"
versionRange: "Router ^1"
verifiedAgainst: "@tanstack/react-router@1.170.18"
contentModel: 2
prerequisites:
  - "React 受控表单与 Router 导航基础"
sourceRefs:
  - "https://tanstack.com/router/latest/docs/guide/navigation-blocking"
---

# 在离开脏表单前阻止导航

> 用 useBlocker 同时处理路由内导航与浏览器卸载，并为自定义确认界面建立明确状态。

**Outcome:** 能只在表单有未保存修改时注册阻塞器，并正确连接继续、留下与关闭标签页行为。

## 阻塞条件应来自真实脏状态

shouldBlockFn 返回 true 表示阻止导航。enableBeforeUnload 负责刷新、关闭标签页等浏览器级卸载；应当只在 isDirty 为 true 时启用，避免用户离开干净页面也收到系统提示。

## 自定义确认需要 resolver

withResolver: true 时，status 变为 blocked 后再显示界面。proceed 允许本次导航，reset 保持当前页面。保存成功后先清除脏状态，避免后续导航再次被旧状态拦截。

> **Note:** 导航阻塞保护用户体验，不会持久化数据，也不能替代服务端并发控制。

File: `src/features/editor/Editor.tsx`

```tsx
import { useBlocker } from '@tanstack/react-router'

function LeaveGuard({ isDirty }: { isDirty: boolean }) {
  const blocker = useBlocker({
    shouldBlockFn: () => isDirty,
    enableBeforeUnload: isDirty,
    withResolver: true,
  })

  if (blocker.status !== 'blocked') return null

  return <div role="alertdialog" aria-modal="true" aria-labelledby="leave-title">
    <h2 id="leave-title">Discard unsaved changes?</h2>
    <button type="button" onClick={blocker.proceed}>Leave</button>
    <button type="button" onClick={blocker.reset}>Stay</button>
  </div>
}
```

## 实践与验证

### 完成检查点

只有表单为脏时才阻止客户端导航和页面卸载，保存成功后立即解除。

### 动手练习

实现继续离开、留在页面和保存后离开三条流程，并为每条流程编写交互测试。

### 验证方法

测试 Link、后退、刷新和保存四种行为；确认框只应在未保存状态出现。

### 常见错误

- 所有导航都弹确认框，即使表单没有变化。
- 只拦截 Router 导航而漏掉浏览器关闭或刷新。

## Official sources

- https://tanstack.com/router/latest/docs/guide/navigation-blocking
