news 2026/10/16 3:18:24

VueUse useTitle 完全指南:在 AIRI 项目中实现响应式页面标题管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VueUse useTitle 完全指南:在 AIRI 项目中实现响应式页面标题管理
  • 人工智能
  • AI Agent
  • MCP 服务
  • 工具调用
  • GUI 自动化
  • 语音
  • 数字人
  • 交互助手

【免费下载链接】airi

💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。

项目地址:https://gitcode.com/moeru-ai/airi
点击查看免费下载

本文讲解 VueUse 的useTitlecomposable —— 一个把document.title变为响应式状态的轻量工具。在 AIRI 的多个 Vue 3 应用(stage-web、stage-pocket、stage-tamagotchi 等)中,各应用index.html目前只有静态<title>(如AIRI),尚未接入动态标题管理;通过useTitle可以基于路由、消息数等响应式状态实时改写浏览器标签页标题,同时借助titleTemplate统一品牌后缀、借助restoreOnUnmount防止组件卸载后标题污染。读完本文,你将掌握useTitle的全部 API 形态、选项语义与类型约束,并能直接在自己的 Vue 3 组件中落地可复用的动态标题方案。

背景:为什么需要响应式标题

浏览器标签页标题(document.title)是 SPA 中最直观的"当前状态"指示器:新消息提醒、正在处理的页面、未保存的变更,都应该反映在标题上。在 AIRI 仓库中,多个前端应用都依赖@vueuse/core(见 apps/stage-pocket/package.json、apps/stage-tamagotchi/package.json、apps/component-calling/package.json 等),且各应用入口 HTML 仅声明了静态标题,例如 apps/stage-pocket/index.html 的<title>AIRI</title>。这意味着标题一旦设置便不会随页面交互变化。useTitle提供了一条更简洁、可维护的替代路径:让标题成为响应式数据的一部分,随ref/computed自动同步。

需要特别注意的是,useTitle属于 VueUse 的 Browser 分类(见 SKILL.md 的函数索引),它直接操作document.title,因此与 SSR 不兼容:在服务端渲染期间你的值只会被客户端应用。如果项目引入 SSR,需要改用其他 head 管理方案。

基本用法:读取与设置标题

useTitle的默认调用不需要任何参数,返回一个可直接读写的响应式ref:

import { useTitle } from '@vueuse/core' const title = useTitle() console.log(title.value) // 打印当前 document.title title.value = 'Hello' // 立即改写页面标题

这段代码背后是标准的ref语义:读取title.value得到当前标题,写入title.value触发document.title同步更新。由于返回对象是Ref<string | null | undefined>,你也可以在<script setup>中直接解构后在模板或逻辑里复用,实现标题的"单一数据源"管理。

设置初始标题

如果希望在调用时就立即把标题设为指定值,直接把字符串作为第一个参数传入即可:

import { useTitle } from '@vueuse/core' const title = useTitle('New Title')

这等价于先读取当前标题再写回,但更简洁。传入的初始值同时也会成为返回ref的初始值,后续读写都围绕它进行。

响应式绑定:让标题随 ref / computed 联动

useTitle的第一个参数接受MaybeRef<string | null | undefined>,即普通字符串、ref或computed都可以。当传入一个响应式来源时,标题会在该来源变化时自动更新。这是最常见的实战形态,例如把标题绑定到未读消息数:

import { useTitle } from '@vueuse/core' import { computed, shallowRef } from 'vue' const messages = shallowRef(0) const title = computed(() => { return !messages.value ? 'No message' : `${messages.value} new messages` }) useTitle(title) // document.title 会与 ref "title" 保持一致

这里computed负责把数值派生为标题字符串,useTitle只做同步,职责单一。消息数变化 →computed重算 → 标签页标题自动刷新,全程无需手写watch或事件监听。该模式同样适用于路由驱动:例如在 apps/stage-pocket/src/main.ts 的router.afterEach钩子旁,可以引入一个基于当前路由 meta 的computed,配合useTitle让每个页面切换后标题自动反映当前页面名称。

统一品牌后缀:titleTemplate

多个页面共用一个应用时,通常希望标题统一带上站点名称,例如对话页面 | AIRI。useTitle支持 Vue Meta 风格的标题模板(titleTemplate),模板中的%s占位符会被替换为实际标题:

import { useTitle } from '@vueuse/core' const title = useTitle('New Title', { titleTemplate: '%s | My Awesome Website', })

执行后标签页标题会显示为New Title | My Awesome Website。对于 AIRI 这类多应用仓库,可以借此把所有页面的标题统一为页面名 | AIRI,与现有静态<title>AIRI</title>(见 apps/stage-web/index.html)的品牌一致性无缝衔接。

titleTemplate的类型为MaybeRef<string> | ((title: string) => string),这意味着它既可以是字符串模板,也可以是接收当前标题并返回完整标题的函数,适合需要条件拼接或国际化(i18n)的场景。

监听外部标题变化:observe

默认情况下useTitle只响应由它自己驱动的值变化。若希望反过来观察document.title被其他代码(第三方脚本、浏览器扩展、其他模块)修改的情况,可以开启observe选项:

const title = useTitle(undefined, { observe: true, })

observe基于MutationObserver监听document.title(实际上监听的是包含标题文本的<title>元素所在节点),任何外部改写都会反向同步回返回的ref,形成双向同步。默认值为false。

两个互斥选项:observe 与 titleTemplate

observe与titleTemplate在类型层面被设计为二选一的互斥关系(详见下文的联合类型声明),不可同时启用:

::: warningobserve与titleTemplate不兼容。 :::

原因从实现语义上可以理解:titleTemplate会改写写出的标题(写入的是模板展开后的结果),此时再观察原始document.title会造成环路或语义混乱;而observe模式下标题以 DOM 中的实际值(而非模板)为唯一事实来源。

组件卸载时还原标题:restoreOnUnmount

restoreOnUnmount用于解决一个常见的 SPA 隐患:组件设置了动态标题,但当组件卸载(如用户离开页面)后标题却停留在最后的状态,导致后续页面顶着错误的标题。启用后,useTitle会在组件卸载(onScopeDispose)时把标题还原为调用前的原始值:

const title = useTitle('Hello', { restoreOnUnmount: true, })

它的类型不仅仅是布尔值,还支持传入自定义还原函数,让你决定还原成什么:

restoreOnUnmount?: false | ((originalTitle: string, currentTitle: string) => string | null | undefined)

还原函数接收两个参数:originalTitle(组件挂载前的原始标题)与currentTitle(当前标题),返回值为还原后的新标题;返回null/undefined时使用默认还原逻辑。这允许做精细化控制,例如"仅当当前标题仍是由本组件设置时才还原,否则保持现状"。

选项速查表

选项类型默认值说明
observebooleanfalse使用MutationObserver观察document.title的外部变化并反向同步;与titleTemplate互斥
titleTemplateMaybeRef<string> \| ((title: string) => string)'%s'标题模板字符串(%s为占位符)或生成完整标题的函数;与observe互斥
restoreOnUnmountfalse \| ((originalTitle, currentTitle) => string \| null \| undefined)—组件卸载时是否/如何还原标题
documentDocument(继承自ConfigurableDocument)当前document指定要操作的目标文档对象,便于在 iframe、测试环境等场景中替换

其中document选项来自ConfigurableDocument类型的继承(UseTitleOptions = ConfigurableDocument & UseTitleOptionsBase),VueUse 中大量 Browser composable 都遵循这一约定,便于单元测试中注入 mock 文档。

返回类型解读

useTitle有两个重载,返回值随调用形态不同而变化:

export type UseTitleReturn = | ComputedRef<string | null | undefined> | Ref<string | null | undefined> // 传入 ReadonlyRefOrGetter 时 useTitle( newTitle: ReadonlyRefOrGetter<string | null | undefined>, options?: UseTitleOptions, ): ComputedRef<string | null | undefined> // 不传或传入 MaybeRef 时 useTitle( newTitle?: MaybeRef<string | null | undefined>, options?: UseTitleOptions, ): Ref<string | null | undefined>

可以推断:当第一个参数是ref或 getter 时,返回ComputedRef(只读的派生标题视图,最终值与传入来源一致);当传入普通字符串或省略参数时,返回可写的Ref,写入操作直接作用于document.title。

完整类型声明

以下是本技能文档(.agents/skills/vueuse-functions/references/useTitle.md)中给出的官方类型声明,可直接作为集成时的类型契约:

export type UseTitleOptionsBase = { /** * Restore the original title when unmounted * @param originTitle original title * @returns restored title */ restoreOnUnmount?: | false | (( originalTitle: string, currentTitle: string, ) => string | null | undefined) } & ( | { /** * Observe `document.title` changes using MutationObserve * Cannot be used together with `titleTemplate` option. * * @default false */ observe?: boolean } | { /** * The template string to parse the title (e.g., '%s | My Website') * Cannot be used together with `observe` option. * * @default '%s' */ titleTemplate?: MaybeRef<string> | ((title: string) => string) } ) export type UseTitleOptions = ConfigurableDocument & UseTitleOptionsBase export type UseTitleReturn = | ComputedRef<string | null | undefined> | Ref<string | null | undefined>

值得注意的设计点是:UseTitleOptionsBase中observe与titleTemplate被建模为互斥的联合类型分支,这是把"两个选项不能同时使用"的约束直接编码进类型系统,让错误配置在编译期就被拦截,而不是留到运行时才暴露。

落地建议:在 AIRI 多应用仓库中的实践模式

结合仓库现状(多个应用已安装@vueuse/core、入口 HTML 仅有静态标题、apps/stage-pocket/src/main.ts 已有路由钩子基础设施),推荐以下实践路径:

  1. 品牌统一:在main.ts或全局布局组件中调用一次useTitle('', { titleTemplate: '%s | AIRI' }),为全站标题注入统一后缀。
  2. 路由联动:利用router.afterEach获取to.meta.title,配合useTitle传入computed,实现页面级动态标题;由于useTitle不兼容 SSR,且这些应用均为纯客户端渲染(main.ts中无 SSR 逻辑),可以安全使用。
  3. 会话级提醒:在聊天等实时场景中,仿照"未读消息数"示例,用computed派生"新消息"提示并写入标题,提升用户注意力引导。
  4. 局部组件:若某个悬浮窗、开发工具面板需要临时改写标题,启用restoreOnUnmount: true,保证组件卸载后标题自动还原,避免状态残留。

useTitle本身是 SKILL.md 中标记为AUTO(适用即用)的 Browser 类函数,只要项目已安装@vueuse/core,即可按上文任一形态直接使用,无需额外安装依赖。

  • 人工智能
  • AI Agent
  • MCP 服务
  • 工具调用
  • GUI 自动化
  • 语音
  • 数字人
  • 交互助手

【免费下载链接】airi

💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。

项目地址:https://gitcode.com/moeru-ai/airi
点击查看免费下载
上一篇:如何为Espruino编写自定义JavaScript库:从入门到精通的完整指南
下一篇:LLaMA-Factory模型评估:自定义评估指标实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 3:18:14

昇腾NPU接入Volcano:从资源建模到调度高可用的实战指南

去年年底&#xff0c;我在帮一个客户搭大模型训练平台&#xff0c;底层是十几台Atlas 800服务器&#xff0c;每台插了8张昇腾910 NPU。平台选型上&#xff0c;Kubernetes负责资源抽象&#xff0c;Volcano作为批处理调度器跑AI训练和推理任务。当时我天真地以为&#xff0c;只要…

作者头像 李华
网站建设 2026/10/16 3:17:51

2024年CSP-J复赛真题及题解: T4 接龙

2024年CSP-J复赛真题及题解: T4 接龙 题目描述 在玩惯了成语接龙之后,小 J 和他的朋友们发明了一个新的接龙规则。 总共有 n n n 个人参与这个接龙游戏,第 i i i 个人会获得一个整数序列 S i S_i

作者头像 李华
网站建设 2026/10/16 3:17:02

时空也只是意识| 赵杰 | 量子意识(第二卷)

作者&#xff1a;赵杰清华大学硕士、微美全息云科技&#xff08;NASDAQ&#xff1a;WIMI&#xff09;董事长、微算法科技&#xff08;NASDAQ&#xff1a;MLGO&#xff09;董事长、育杰奖学金创始人按人类意识的认知逻辑&#xff0c;宇宙空间始终与时间绑定成 “动态整体”&…

作者头像 李华
网站建设 2026/10/16 3:16:54

华工科技领投GEO赛道后,厦门企业如何核验服务商真实交付能力

一笔融资背后的赛道信号10月13日&#xff0c;深度云海宣布完成千万级天使轮融资&#xff0c;领投方是A股上市公司华工科技。这是产业资本首次领投GEO赛道。华工科技投资总监李钰的判断值得留意&#xff1a;GEO已从监测工具进化为优化工具&#xff0c;长期将走向可自动执行的Age…

作者头像 李华
网站建设 2026/10/16 3:14:29

Odoo模块继承核心套路:模型、视图、方法重写全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华