使用 VueUse useTextDirection 为 airi 应用实现响应式 LTR/RTL 文本方向
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
本文聚焦 airi 项目所采用的 VueUse Browser 类别 composable 之一 ——useTextDirection,系统讲解如何以响应式方式读取并双向控制 DOM 元素的dir全局属性,从而在中文、日文、韩文等 LTR 语境与阿拉伯语、希伯来语等 RTL 语境之间动态切换排版方向。读完本文,你将掌握useTextDirection的默认行为、全部 Options 参数、返回值语义与底层实现原理,并能将其接入 airi 各 stage 应用的多语言(i18n)体系,为未来的 RTL 语言支持做好准备。
认识 useTextDirection:对dir属性的响应式封装
useTextDirection是 VueUse 中用于「元素文本方向」的响应式工具,其官方定位为:
Reactive dir of the element's text.
它围绕 HTML 的dir全局属性 建模。dir属性用于声明元素文本的书写方向,可取三个值:
ltr(left-to-right):从左到右,绝大多数语言(含中文、英文、日文、韩文)的默认方向;rtl(right-to-left):从右到左,用于阿拉伯语、希伯来语、波斯语等语言;auto:由浏览器根据元素内容自动推断方向。
在 airi 仓库中,该函数被收录在 .agents/skills/vueuse-functions/SKILL.md 的 Browser 分类下(Invocation规则为AUTO,即当开发 Vue.js / Nuxt 功能时若需求匹配即可直接采用),其详细参考文档位于 .agents/skills/vueuse-functions/references/useTextDirection.md。仓库本身通过 pnpm workspace 的 catalog 机制统一管理 VueUse 依赖版本,pnpm-workspace.yaml 中声明:
'@vueuse/core': ^14.4.0这意味着 airi 下的多个应用与包(如 apps/stage-tamagotchi/package.json、apps/stage-web/package.json、apps/stage-pocket/package.json、packages/stage-layouts/package.json、packages/electron-vueuse/package.json 等)均以"@vueuse/core": "catalog:"的方式引用同一份 14.4.x 版本,useTextDirection在这些应用中可直接导入使用。
快速上手:一行代码读取当前文本方向
useTextDirection的默认用法极为简洁:
import { useTextDirection } from '@vueuse/core' const dir = useTextDirection() // Ref<'ltr' | 'rtl' | 'auto'>不传任何参数时,它默认以html标签为目标元素。也就是说,返回的dir引用会反映<html>元素上dir属性的当前值。参考文档给出了两种典型场景:
<!--ltr--> <html> ... </html> <!--rtl--> <html dir="rtl"> ... </html>当页面根节点<html>未设置dir时,函数返回初始值(默认'ltr');当<html dir="rtl">被设置后,dir引用即变为'rtl'。由于返回值是一个WritableComputedRef(见下文「返回值与类型声明」),你既可以读取方向,也可以直接赋值来切换方向,赋值会同步写回目标元素的dir属性。
Options 参数详解:selector、observe 与 initialValue
useTextDirection接受一个可选的UseTextDirectionOptions配置对象,共三个参数:
import { useTextDirection } from '@vueuse/core' const mode = useTextDirection({ selector: 'body' }) // Ref<'ltr' | 'rtl' | 'auto'>| 参数 | 类型 | 默认值 | 作用 |
|---|---|---|---|
selector | string | 'html' | 目标元素的 CSS 选择器,决定读取/写入dir的元素 |
observe | boolean | false | 是否用MutationObserver观察目标元素上dir属性的变化,从而让返回值随外部修改自动更新 |
initialValue | UseTextDirectionValue | 'ltr' | 目标元素未设置dir属性时使用的初始值 |
三个参数各自的适用场景如下:
selector:默认作用于整个文档根节点html,适合「全局排版方向」这种页面级语义;当需要让某个容器(例如对话消息区、代码面板)独立于全局采用相反方向时,可改为selector: 'body'、selector: '.chat-panel'等任意合法 CSS 选择器。observe:默认false时,返回值只在组件内部赋值时更新;设为true后,函数内部通过MutationObserver监听目标元素dir属性的变化,即使方向由组件外部的代码(如框架级 i18n 插件、浏览器扩展)修改,返回值也会保持同步。initialValue:当目标元素完全未声明dir时作为回退值。由于大多数语言默认即ltr,默认值'ltr'在绝大多数场景下无需修改;若你的应用默认面向 RTL 语言,可显式传入'rtl'。
返回值与类型声明
useTextDirection的类型签名如下(源自参考文档的 Type Declarations):
export type UseTextDirectionValue = "ltr" | "rtl" | "auto" export interface UseTextDirectionOptions extends ConfigurableDocument { /** * CSS Selector for the target element applying to * * @default 'html' */ selector?: string /** * Observe `document.querySelector(selector)` changes using MutationObserver * * @default false */ observe?: boolean /** * Initial value * * @default 'ltr' */ initialValue?: UseTextDirectionValue } /** * Reactive dir of the element's text. * * @__NO_SIDE_EFFECTS__ */ export declare function useTextDirection( options?: UseTextDirectionOptions, ): WritableComputedRef<UseTextDirectionValue, UseTextDirectionValue>值得注意的语义细节:
值域收窄:
UseTextDirectionValue严格限定为'ltr' | 'rtl' | 'auto'三个字面量,类型系统会在编译期拦截非法赋值。可写计算引用:返回类型是
WritableComputedRef<UseTextDirectionValue, UseTextDirectionValue>,这意味着dir既是一个响应式「读」入口,也是一个响应式「写」入口。例如:// 读取 console.log(dir.value) // 'ltr' | 'rtl' | 'auto' // 写入:切换为 RTL,并同步更新目标元素属性 dir.value = 'rtl'继承
ConfigurableDocument:Options 接口扩展自ConfigurableDocument,因此在需要时也可以传入自定义的document实例(例如iframe.contentDocument或测试环境中的模拟 DOM),从而把方向控制限定在特定文档上下文中。
底层实现原理:从类型声明反推工作方式
参考文档并未直接给出函数体源码,但从其类型声明与行为描述可以清晰推断其实现脉络:
- 目标元素解析:通过
document.querySelector(selector)(selector默认'html')定位要操作的元素。这与useCssVar、useElementBounding等 VueUse 元素类 composable 的取址方式一致。 - 读取与回退:读取目标元素的
dir属性值;若属性缺失,则回退到initialValue(默认'ltr')。这解释了「<html>未标注方向时返回ltr」的默认行为。 - 写入同步:返回值是
WritableComputedRef,其 setter 会调用setAttribute('dir', value)写回目标元素,保证「ref 赋值 → DOM 属性变更」的闭环。 - 响应式观察:当
observe: true时,内部挂载一个MutationObserver观察目标元素的dir属性(attributes变化),一旦外部修改属性,立即把新值推回流中更新 ref;组件卸载时自动断开观察,避免泄漏。
基于上述机制,从源码结构可以推断:useTextDirection本质上是「DOM 属性 ↔ 响应式 ref」的双向绑定层,行为语义与useDark(主题类名双向绑定)同构,只是把操作对象从class换成了dir属性,并额外提供了MutationObserver这一可选的外部变更监听通道。
在 airi 多语言应用中的落地场景
airi 是一个自带多语言内容体系的项目(docs 下同时维护en、ja、ko、zh-Hans等多份语言文档),其各 stage 应用(Web / Electron 桌面端 / 移动端)均实现了语言检测逻辑。以 apps/stage-tamagotchi/src/renderer/modules/i18n.ts、apps/stage-web/src/modules/i18n.ts 为例,语言初始化逻辑形如:
language = navigator.language || 'en'即优先读取浏览器/系统语言作为兜底。而 apps/stage-tamagotchi/src/renderer/composables/use-language.ts 则负责在 Electron 渲染进程与主进程配置之间同步语言选择,并带有「首次启动时不把navigator.language回写进主进程配置」的保护逻辑。
useTextDirection恰好可以在这套语言体系中充当「方向层」:
import { useTextDirection } from '@vueuse/core' import { useI18n } from 'vue-i18n' const { locale } = useI18n() const dir = useTextDirection({ observe: true }) // 语言切换时同步更新页面书写方向 const RTL_LOCALES = ['ar', 'he', 'fa', 'ur'] // 示例:RTL 语言标签集合 watch(locale, (value) => { dir.value = RTL_LOCALES.some(code => value.startsWith(code)) ? 'rtl' : 'ltr' })同时,方向属性可以配合 CSS 的选择器能力实现样式自适应。当dir被设置到html或具体容器上后,CSS 可通过属性选择器[dir="rtl"]或:dir(rtl)伪类编写双向布局规则,例如:
.chat-log[dir="rtl"] { direction: rtl; text-align: right; }dir属性本身还具备继承语义:设置在html上的方向会作用于整棵文档树,而局部容器可覆盖为dir="ltr"以强制某块内容(如代码块、URL、电话号码)保持从左到右的排版,这符合 HTML 规范对dir的级联规则。
使用注意事项
- SSR 场景:
useTextDirection属于浏览器 DOM 类 composable,服务端渲染阶段不存在document,应仅在客户端挂载后使用,或参考useMounted/tryOnMounted的时机约定延迟初始化;这正是其 Options 继承ConfigurableDocument的意义所在——必要时可注入可用的文档实现。 - observe 的开销:
MutationObserver默认false是有意为之。只有当你确实需要「外部修改dir属性也能反向驱动 ref」时才开启,避免在非必要场景下为每个目标元素维护观察器。 - 与相关 composable 的区分:
useTextDirection关注的是元素自身的dir属性;若需求变为「根据用户首选语言推断方向」,可组合usePreferredLanguages与useTextDirection使用,前者读取navigator.languages,后者负责把结论落到dir属性上,二者职责互补。 - 值域校验:由于返回值类型收窄为
'ltr' | 'rtl' | 'auto',赋值非法字符串会在类型检查阶段被拦截,这也是一种编译期安全保证。
小结
useTextDirection是 VueUse 面向「文本方向」这一细分领域提供的标准答案:默认以html为目标,支持通过selector精确指定任意元素,通过observe开启MutationObserver双向同步,通过initialValue控制缺失属性时的回退值,并返回一个可读可写的WritableComputedRef<'ltr' | 'rtl' | 'auto'>。在 airi 这类具备多语言内容与多端形态(Web / Electron / Capacitor)的项目中,将其接入现有 i18n 语言切换逻辑即可低成本地为未来的 RTL 语言版本铺平排版基础。
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考