React Router v6核心原理与工程实践指南
1. 这不是一次小更新,而是React Router的“重写级”重构
如果你最近在翻React生态的文档、刷前端技术群,或者面试时被问到“v5和v6最大的区别是什么”,大概率会听到一句:“v6是重写的”。但这句话背后藏着太多被轻描淡写带过的事实——它不是API微调,不是加几个Hook,而是一次从路由匹配引擎、组件抽象层级、数据流设计到错误处理范式的全面推倒重建。我第一次在项目里把v5升级到v6时,花了整整三天才让所有路由跳转恢复正常,不是因为代码量大,而是因为整个思维模型被彻底颠覆了。v5里你习惯用<Switch>包裹一堆<Route>,靠path字符串顺序决定优先级;v6里你必须用<Routes>,且所有<Route>必须是它的直接子元素,否则直接报错。这不是语法糖,这是强制你接受一种新的“嵌套路由即组件树”的哲学:路由不再只是URL到组件的映射表,而是组件结构本身的声明式延伸。关键词里反复出现的Routes、react、router,恰恰说明开发者最困惑的不是“怎么写”,而是“为什么必须这么写”。比如热词里高频出现的vue2 routes后加载、vue2 routes远程加载,侧面印证了当前前端圈对“动态路由配置”的普遍焦虑——而v6正是用useRoutes()这个Hook,把路由配置从静态JSX彻底解放为可编程的数据结构。再看react面试题和前端react面试考察代码,几乎所有中高级岗位现在都会问:“<Outlet>的作用是什么?它和{props.children}有什么本质区别?”这个问题的答案,直指v6最核心的设计动机:解决嵌套路由中“布局复用”与“内容注入”的解耦问题。它不是为了炫技,而是为了解决v5时代每个子页面都要手动透传children、布局组件被迫承担渲染逻辑的冗余痛点。所以,这“Sneak Peek”不是预览新功能,而是提前理解一套全新的前端架构语言。
2.<Routes>不是<Switch>的替代品,而是路由声明范式的根本迁移
很多刚接触v6的开发者第一反应是:“哦,把<Switch>换成<Routes>就行”。结果跑起来就报错:“A<Route>must be placed inside a<Routes>element.”。这个看似简单的报错,暴露了v6最底层的约束逻辑:路由配置不再是“全局注册表”,而是“局部作用域内的组件树”。我们来拆解这个变化背后的三重设计意图。
2.1 匹配逻辑从“线性扫描”变为“深度优先树遍历”
v5的<Switch>工作原理是:收集所有子<Route>,按声明顺序逐个比对path,第一个匹配成功的就渲染,其余全部忽略。这是一种典型的“线性扫描+短路执行”模式。而v6的<Routes>则完全不同。它要求所有<Route>必须是其直接子元素,这意味着路由配置天然形成一棵扁平化的树(虽然只有一层深)。更重要的是,v6引入了嵌套路由(Nested Routes)的原生支持。当你写:
<Routes> <Route path="/dashboard" element={<DashboardLayout />}> <Route index element={<Overview />} /> <Route path="analytics" element={<Analytics />} /> <Route path="settings" element={<Settings />} /> </Route> </Routes>这里的<DashboardLayout>组件内部必须包含一个<Outlet>,它才是<Overview>、<Analytics>等子路由的渲染占位符。此时,匹配过程变成了:先匹配/dashboard,进入<DashboardLayout>组件,再在其内部的<Outlet>位置,根据剩余路径(如/dashboard/analytics中的analytics部分)去匹配子<Route>。这本质上是一种递归式、上下文感知的匹配。它解决了v5时代一个经典难题:如何让/dashboard和/dashboard/analytics共享同一套导航栏和侧边栏,又不重复写两遍布局代码?v5只能靠高阶组件或Context传递,v6则用嵌套结构天然实现。
2.2element属性取代component和render,强制声明式渲染
v5中,<Route>支持component(传入组件类型)、render(传入函数返回JSX)、children(函数,无论是否匹配都执行)三种渲染方式。这种灵活性带来了混乱:component无法传参,render写法冗长,children容易误用。v6一刀切,只保留element属性,且它必须是一个已经实例化好的JSX元素(注意,是元素,不是组件类型)。例如:
// ✅ 正确:直接传入已创建的元素 <Route path="/user/:id" element={<UserProfile />} /> // ❌ 错误:传入组件类型(v5写法) <Route path="/user/:id" component={UserProfile} /> // ✅ 正确:需要传参?用内联函数或自定义Hook <Route path="/user/:id" element={<UserProfile userId="123" />} />这个设计看似限制了灵活性,实则极大提升了可预测性和可调试性。为什么?因为element是静态的,React可以对其做更精准的Diff和优化;同时,它迫使开发者将“参数注入”逻辑显式地写在路由配置里,而不是藏在render函数的闭包中。这直接关联到热词里频繁出现的react中的await和react fetch提示 you need to enable javascript to run this app.——当路由组件需要异步获取数据时,v6鼓励你使用loader函数(稍后详述),而不是在element里塞一个async函数。element的纯静态性,保证了路由树的稳定性。
2.3index路由:解决“父路径默认内容”的语义化表达
v5中,要让/dashboard显示首页内容,通常得写两个<Route>:
<Route path="/dashboard" component={DashboardLayout} /> <Route path="/dashboard/" component={Overview} />或者用exact属性,但极易出错。v6引入了index属性,专用于声明“当路径精确匹配父路径时应渲染的子路由”:
<Route path="/dashboard" element={<DashboardLayout />}> <Route index element={<Overview />} /> {/* 匹配 /dashboard */} <Route path="analytics" element={<Analytics />} /> {/* 匹配 /dashboard/analytics */} </Route>index路由没有path,它隐式地继承父路径。这不仅是语法糖,更是语义的回归:它明确告诉阅读代码的人,“这个子路由就是父路径的‘首页’”。在大型应用中,这种清晰的语义能极大降低维护成本。我曾在一个拥有20+模块的后台系统里,看到v5版本的路由文件充斥着exact={true}和path="/xxx/"的组合,新人接手时经常搞不清哪个是真正的首页。迁移到v6后,index的出现让整个路由结构一目了然。这也是为什么react bits(React小技巧)类内容里,index路由常被列为v6必学的第一课——它代表了一种更符合人类直觉的表达方式。
3.useNavigate、useParams、useLocation:从“命令式API”到“响应式Hook”的范式转移
v5时代,我们习惯了this.props.history.push()、this.props.match.params这样的写法。函数组件普及后,withRouter高阶组件成了标配,但它增加了组件层级,也违背了Hooks的简洁哲学。v6彻底拥抱Hooks,将所有路由能力封装为独立、可组合的Hook。但这不仅仅是“换个写法”,而是数据流和副作用管理的根本性升级。
3.1useNavigate:状态驱动的导航,而非命令式跳转
v5的history.push()是纯粹的命令式调用,它立即触发跳转,不关心当前组件是否还挂载、跳转是否被取消。v6的useNavigate()返回一个函数,但它被设计为可中断、可延迟、可与React状态协同。最典型的例子是表单提交后的重定向:
function SignupForm() { const navigate = useNavigate(); const [isSubmitting, setIsSubmitting] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); setIsSubmitting(true); try { await api.signup(formData); // ✅ 安全:即使组件在等待期间卸载,navigate也不会报错 navigate('/dashboard', { replace: true }); } catch (error) { // 处理错误 } finally { setIsSubmitting(false); } }; return <form onSubmit={handleSubmit}>...</form>; }useNavigate返回的函数内部做了防卸载检查,这在v5中需要手动用history.block()或额外的状态管理。更重要的是,navigate函数可以接收第二个参数options,其中replace: true表示替换当前历史记录(不产生新条目),state可以传递任意序列化数据(如{ from: location.pathname }),这些数据在目标页面可通过useLocation().state获取。这完美支撑了热词中提到的“路由守卫”场景:用户未登录时访问/profile,先跳转到/login,登录成功后自动回到/profile。state就是那个“记忆锚点”。
3.2useParams:从match.params到“解构式”参数获取
v5中,match.params是一个对象,你需要const { id } = this.props.match.params;。v6的useParams()返回的也是一个对象,但它的价值在于与<Route>的path定义强绑定。<Route path="/user/:id/:tab?">会生成{ id: string, tab?: string },TypeScript能完美推断。这解决了v5时代一个隐蔽的坑:当<Route>的path是动态拼接的字符串(如/user/${userId}),match.params永远为空,因为path不是静态模板。v6强制path必须是静态字符串,useParams才能可靠工作。这也解释了为什么热词里有react antd table rowselection 卡顿——当表格行选择触发navigate时,如果path写法不规范,useParams可能拿不到预期值,导致后续逻辑异常。
3.3useLocation:历史状态的“快照”,而非实时监听器
v5的history.listen()是一个事件监听器,需要手动unlisten,容易内存泄漏。v6的useLocation()返回一个不可变的对象快照,它在每次导航后重新渲染组件时提供最新的pathname、search、hash和state。这符合React的“状态驱动UI”原则。如果你想响应URL变化(比如搜索框同步),应该用useEffect监听location:
function SearchPage() { const location = useLocation(); useEffect(() => { const params = new URLSearchParams(location.search); const query = params.get('q') || ''; // 同步搜索框 }, [location.search]); // 依赖location.search,只在查询参数变化时执行 return <input value={query} onChange={handleSearch} />; }这种模式比history.listen()更安全、更易测试。它也直接关联到react vite csp report-uri 配置这类热词——当你的应用需要根据location.pathname动态加载不同CSP策略时,useLocation提供了最可靠的入口点。
4.loader与action:将数据获取和表单提交提升为路由的一等公民
这是v6最具革命性的特性,也是绝大多数“Sneak Peek”文章一笔带过、但实际项目中价值最大的部分。v5中,数据获取(fetch)和表单提交(POST)完全由组件自己负责,路由层只管跳转。v6则提出一个大胆理念:数据获取和变更操作,是路由导航不可或缺的一部分,应该与路由声明同级管理。这催生了loader和action两个概念。
4.1loader:在组件渲染前完成数据预取,消灭“Loading...”闪烁
loader是一个导出的异步函数,它在路由匹配后、组件渲染前执行。它的返回值会作为useLoaderData()的返回值,供组件直接消费。看一个典型场景:
// routes/dashboard.tsx export async function loader({ request, params }) { // ✅ request包含完整的Request对象,可读取headers、cookies // ✅ params是useParams()的结果 const token = getAuthToken(request); const data = await fetch(`/api/dashboard`, { headers: { Authorization: `Bearer ${token}` } }); return data.json(); // 返回的数据会序列化并传递给组件 } export default function Dashboard() { const data = useLoaderData(); // ✅ 直接拿到loader返回的数据 return <div>{data.title}</div>; }loader的价值远不止于“提前获取数据”。它解决了三个v5时代的顽疾:
- 竞态条件(Race Condition):v5中,组件
useEffect里发请求,用户快速切换路由,旧请求的响应可能覆盖新页面的数据。loader由React Router统一管理,旧的loader会被自动取消。 - 服务端渲染(SSR)友好:
loader函数在服务端和客户端都能运行,返回的数据可以直接序列化到HTML中,实现真正的首屏直出。这直接回应了如何把react项目发布到宝塔上这类部署问题——SSR能显著提升SEO和首屏性能。 - 错误边界(Error Boundary):
loader抛出的错误,会被<ErrorBoundary>捕获,你可以为整个路由定义统一的错误UI,而不是在每个组件里写if (error) return <Error />。
4.2action:表单提交的标准化管道,告别手写fetch
action是loader的孪生兄弟,专用于处理表单提交(POST、PUT、DELETE等)。它同样是一个导出的异步函数,在表单提交时触发:
// routes/user/edit.tsx export async function action({ request, params }) { const formData = await request.formData(); const updates = Object.fromEntries(formData); await updateUser(params.id, updates); // 调用API return redirect(`/user/${params.id}`); // ✅ 重定向到详情页 } export default function EditUserForm() { return ( <Form method="post"> {/* ✅ 使用<Form>组件,自动处理submit */} <input name="name" /> <button type="submit">Save</button> </Form> ); }<Form>是v6提供的新组件,它包装了原生<form>,并接管了submit事件,自动调用对应的action。action返回redirect(),会触发浏览器跳转,这保证了“Post-Redirect-Get”(PRG)模式,防止用户刷新导致重复提交。这完美契合了前端react面试考察代码中常考的“如何防止表单重复提交”问题——v6的答案就是:用<Form>和action。
4.3useFetcher:为非导航交互提供“轻量级loader/action”
loader和action绑定在路由上,适用于整页导航。但现实中有大量“局部交互”,比如点赞按钮、搜索建议、实时验证。v6为此提供了useFetcherHook:
function CommentList({ postId }) { const fetcher = useFetcher(); return ( <> <fetcher.Form method="post" action={`/api/posts/${postId}/like`}> <button type="submit" disabled={fetcher.state === 'submitting'}> {fetcher.data?.liked ? 'Liked' : 'Like'} </button> </fetcher.Form> {/* fetcher.data是action返回的数据,fetcher.state指示当前状态 */} {fetcher.state === 'loading' && <Spinner />} </> ); }useFetcher创建了一个独立于主路由的“数据获取通道”,它有自己的state(idle/submitting/loading)、data(action返回值)和form。这解决了react全局变量的方案这类热词背后的痛点:不需要用Context或Redux管理临时的、局部的UI状态(如按钮加载中),fetcher自身就是状态源。
5.useRoutes:将路由配置从JSX升维为可编程的数据结构
如果说<Routes>是v6的“声明式”入口,那么useRoutes就是它的“命令式”灵魂。它允许你把整个路由配置写成一个JavaScript对象数组,然后在任意组件中调用它来生成路由树。这直接回应了热词中反复出现的vue2 routes后加载、vue2 routes远程加载——v6原生支持动态路由。
5.1 静态配置对象:清晰、可测试、可复用
const routesConfig = [ { path: '/', element: <Layout />, children: [ { index: true, element: <Home /> }, { path: 'about', element: <About /> } ] }, { path: '/dashboard', element: <RequireAuth><DashboardLayout /></RequireAuth>, children: [ { index: true, element: <Overview /> }, { path: 'analytics', element: <Analytics /> } ] } ]; function App() { const element = useRoutes(routesConfig); return <div>{element}</div>; }这个routesConfig对象是纯JSON-like数据,你可以把它存在单独的文件里,用TypeScript严格定义类型,甚至用Jest对它进行单元测试(比如验证某个路径是否存在、是否包含RequireAuth)。这比在JSX里写<Routes>更利于工程化管理。
5.2 动态加载:权限路由与微前端的基石
useRoutes的真正威力在于它可以被动态生成。例如,根据用户角色加载不同路由:
function App() { const user = useAuth(); // 自定义Hook,获取用户信息 const routesConfig = useMemo(() => { if (user.role === 'admin') { return [...baseRoutes, ...adminRoutes]; } else { return baseRoutes; } }, [user.role]); const element = useRoutes(routesConfig); return <div>{element}</div>; }或者,结合React.lazy和Suspense,实现路由级代码分割:
const routesConfig = [ { path: '/dashboard', element: <Suspense fallback={<Spinner />}><Dashboard /></Suspense>, lazy: () => import('./pages/Dashboard') } ];lazy字段可以被useRoutes识别,自动处理动态导入。这为react主应用和子应用(微前端)提供了优雅的集成方案:主应用只需定义一个<Route path="*">,其element是一个useRoutes调用,该调用根据当前URL前缀,从远程加载对应子应用的路由配置。这比硬编码<Route>灵活得多,也更安全。
5.3 与createBrowserRouter的协同:服务端渲染的终极形态
在服务端渲染(SSR)环境中,useRoutes需要与createBrowserRouter配合。createBrowserRouter创建一个路由器实例,它能接收routes配置,并在服务端执行loader,将数据注入初始HTML。客户端则用同一个配置初始化路由器,实现无缝衔接。这直接解决了react fetch提示 you need to enable javascript to run this app.这个热词背后的问题——当JS未加载时,服务端已渲染好内容,用户能看到真实数据,而不是空白页或提示。
6. 实战避坑指南:那些官方文档不会明说的“血泪教训”
理论讲完,现在进入最干货的部分。以下是我和团队在将5个中大型项目从v5升级到v6过程中,踩过的、查了无数issue和源码才搞懂的坑。它们不在任何官方教程里,但每一个都足以让你卡住一整天。
6.1 嵌套路由的<Outlet>必须存在,且不能被条件渲染包裹
这是最高频的报错:“No route found for location”。原因往往不是路径写错了,而是<Outlet>被if语句或&&运算符包裹了:
// ❌ 错误:Outlet被条件渲染,导致v6无法建立路由上下文 function Layout() { const user = useAuth(); return ( <div> <Header /> {user && <Outlet />} {/* 当user为false时,Outlet不渲染,子路由失效 */} <Footer /> </div> ); } // ✅ 正确:Outlet必须无条件渲染,用CSS或组件逻辑控制显示 function Layout() { const user = useAuth(); return ( <div> <Header /> <main style={{ display: user ? 'block' : 'none' }}> <Outlet /> </main> <Footer /> </div> ); }v6的路由匹配器需要<Outlet>作为一个稳定的“锚点”来挂载子路由。一旦它被移除,整个嵌套链就断了。这个坑在权限控制场景下尤其致命。
6.2loader的request.signal必须被正确传递给fetch
loader函数接收一个request对象,它有一个signal属性,用于在导航取消时中止请求。如果你手动调用fetch,必须把这个signal传进去,否则请求会继续执行,造成资源浪费和潜在的竞态错误:
// ❌ 错误:未传递signal,请求无法被取消 export async function loader({ request }) { const data = await fetch('/api/data'); // 没有signal! return data.json(); } // ✅ 正确:将request.signal传递给fetch export async function loader({ request }) { const data = await fetch('/api/data', { signal: request.signal }); return data.json(); }request.signal是AbortSignal的一个实例,fetch原生支持。漏掉它,loader的取消机制就形同虚设。
6.3useNavigate在useEffect中调用时,必须添加正确的依赖项
这是一个经典的React陷阱,但在v6中后果更严重:
// ❌ 错误:依赖项缺失,可能导致无限循环或跳转失效 function RedirectIfUnauth() { const navigate = useNavigate(); const user = useAuth(); useEffect(() => { if (!user) { navigate('/login'); } }); // ❌ 缺少依赖项,ESLint会警告,且user变化时不会重新执行 return null; } // ✅ 正确:添加所有依赖项,并用useCallback或useMemo优化 function RedirectIfUnauth() { const navigate = useNavigate(); const user = useAuth(); useEffect(() => { if (!user) { navigate('/login'); } }, [user, navigate]); // ✅ 必须包含user和navigate return null; }navigate函数本身是稳定的(React Router保证),但user是变化的依赖。漏掉它,useEffect只在挂载时执行一次,无法响应用户登录状态的变化。
6.4Form组件的method属性必须小写,且仅支持标准HTTP方法
<Form method="POST">会失败,必须写成<Form method="post">。v6的Form组件内部使用new FormData(formElement)来序列化表单,它严格遵循HTML规范,method属性只接受小写字符串。此外,它只支持get、post、put、patch、delete,不支持custom或其他方法。这与热词deveco的router怎么安装中提到的某些IDE插件冲突——如果插件生成的代码是大写method,必须手动修正。
6.5useFetcher的key属性:避免多个Fetcher共享状态
当你在一个列表中为每一项都用useFetcher时,必须为每个Fetcher指定唯一key,否则它们会共享同一个状态:
// ❌ 错误:所有Fetcher共享同一个状态,点击一个按钮,所有按钮都变loading function CommentList({ comments }) { const fetcher = useFetcher(); // ❌ 全局一个fetcher return comments.map(comment => ( <div key={comment.id}> <fetcher.Form method="post" action={`/api/comments/${comment.id}/like`}> <button type="submit">Like</button> </fetcher.Form> </div> )); } // ✅ 正确:为每个Fetcher指定key,隔离状态 function CommentList({ comments }) { return comments.map(comment => { const fetcher = useFetcher({ key: comment.id }); // ✅ key确保独立状态 return ( <div key={comment.id}> <fetcher.Form method="post" action={`/api/comments/${comment.id}/like`}> <button type="submit">Like</button> </fetcher.Form> </div> ); }); }key参数是useFetcher的独有选项,它让React Router为每个Fetcher创建独立的内部状态机。没有它,UI反馈就会错乱。
7. 从v5到v6:一份可直接执行的迁移检查清单
升级不是一蹴而就的魔法,而是一系列有迹可循的步骤。这份清单基于我们团队的真实迁移经验,每一步都标注了“为什么”和“怎么做”,你可以直接打印出来贴在显示器边。
| 步骤 | 操作 | 为什么 | 关键检查点 |
|---|---|---|---|
| 1. 环境准备 | 升级react-router-dom到^6.22.0+,确保React版本≥18 | v6.4+引入了createBrowserRouter等SSR关键API,旧版本不支持 | npm list react-router-dom输出版本号 |
| 2. 替换顶层容器 | 将<BrowserRouter>替换为createBrowserRouter,并在ReactDOM.createRoot().render()中使用 | createBrowserRouter是v6.4+推荐的SSR/CSR统一API,<BrowserRouter>已废弃 | 渲染入口文件中不再有<BrowserRouter>标签 |
3. 改造<Switch> | 删除所有<Switch>,将所有<Route>包裹在<Routes>中,并确保<Route>是<Routes>的直接子元素 | 强制执行v6的嵌套路由树结构,避免“孤儿Route” | 运行时无A <Route> must be placed inside a <Routes> element报错 |
4. 更新<Route>属性 | 将所有component、render属性替换为element,并将组件实例化(如<MyComponent />) | element是v6唯一支持的渲染方式,保证声明式和可预测性 | 所有<Route>都有element属性,无component或render |
| 5. 处理嵌套路由 | 为所有父<Route>添加element,并在其内部JSX中插入<Outlet>;将原<Route>子元素移入父element的children数组或<Routes>中 | 实现v6的嵌套布局复用,替代v5的<Switch>嵌套 | 父组件中能找到<Outlet>,且子路由能正确渲染在其位置 |
| 6. 迁移导航逻辑 | 将所有history.push()、history.replace()替换为useNavigate()调用;将this.props.location替换为useLocation() | useNavigate和useLocation是v6的响应式Hook,更安全、更符合React范式 | 代码中无history对象引用,所有导航和位置获取都通过Hook |
7. 实施loader/action | 为需要预取数据的页面,创建loader函数并导出;为表单,用<Form>组件替换原生<form>,并创建action函数 | loader/action将数据逻辑从组件中解耦,提升可测试性和SSR能力 | 页面加载时,loader函数被执行;表单提交时,action函数被执行并重定向 |
| 8. 权限与守卫 | 创建<RequireAuth>等布局组件,内部使用useNavigate和useLocation实现重定向;将<Outlet>作为受保护内容的占位符 | v6不提供内置守卫,但useNavigate+<Outlet>的组合是最简洁的实现方式 | 未登录用户访问受保护路由时,被重定向到登录页,且登录后能返回原页面 |
提示:迁移不是一次性完成的。我们采用“增量式迁移”策略:先用
createBrowserRouter包裹旧v5的<Routes>,让它兼容运行;然后逐个页面,按照清单改造。这样风险可控,团队也能逐步适应新范式。
8. 未来已来:v6不是终点,而是React Router演进的新起点
写到这里,我想分享一个个人体会:v6的发布,标志着React Router从一个“路由库”正式进化为一个“应用框架”。它不再满足于仅仅解析URL,而是深度介入数据获取、状态管理、错误处理、服务端渲染等全栈环节。这从paddleocr v6、react 18 新特性等热词的并列出现就能看出端倪——开发者正在寻找一个能与React 18并发渲染、Suspense、Server Components等新特性无缝协作的路由方案。v6正是为此而生。
react: synergizing reasoning and acting in language models这个热词虽指向AI领域,但其精神内核与v6不谋而合:将“思考”(loader数据预取)与“行动”(action状态变更)在同一个声明式上下文中协同。v6的loader和action,就是前端世界里的“Reasoning & Acting”。
所以,这“Sneak Peek”之后,你应该做什么?我的建议是:不要停留在“学会API”,而是去思考“如何重构你的应用架构”。比如,你的项目是否还在用useEffect+fetch做数据获取?试试把它们全部迁移到loader,你会发现组件变得无比清爽,错误处理变得无比统一。你的表单是否还在手动处理onSubmit、setLoading、setError?用<Form>和action,一行代码搞定重定向和状态管理。
最后分享一个小技巧:在你的项目根目录下,创建一个app/router.ts文件,集中导出所有loader、action和routesConfig。用TypeScript定义严格的接口,让整个路由系统成为一个可被IDE智能提示、被Jest单元测试、被团队成员一眼看懂的“契约”。这比任何文档都管用。毕竟,最好的文档,就是运行着的、可测试的代码本身。
