Vue3 零基础每日笔记(036):动手封装 useTitle 与 useToggle——最小的组合式函数

一、前言

上篇懂了"为什么",本篇开始"动手"。选两个最简单的下手:useTitle (响应式改网页标题)和 useToggle(布尔开关)。麻雀虽小五脏俱全------返回值怎么设计、初始值怎么兜底、怎么命名,组合式函数的全部基础规范都在这两个十行函数里。

二、useTitle:让网页标题变成响应式数据

目标 API :const title = useTitle(),改 title.value 页面标签实时变。

先想清楚实现三步:读当前标题作初始值 → 数据变时写回 document.title → 返回数据。

js 复制代码
// src/composables/useTitle.js
import { ref, watch } from 'vue'

export function useTitle(newTitle) {
  // 初始值:传了就用传的,没传就读当前标题
  const title = ref(newTitle ?? document.title)

  // 数据变 → 写回浏览器标签
  watch(title, (val) => {
    document.title = val
  }, { immediate: true })   // immediate:进来先同步一次

  return title
}
vue 复制代码
<!-- 使用 -->
<template>
  <p>页面标签实时变化:</p>
  <button @click="title = `(${count} 条未读)我的应用`">模拟新消息</button>
  <button @click="title = '我的应用'">清除未读</button>
  <p>{{ count }} 条未读</p>
</template>

<script setup>
import { ref } from 'vue'
import { useTitle } from '@/composables/useTitle'

const count = ref(3)
const title = useTitle('我的应用')
</script>

设计复盘(每个细节都有讲究):

  1. newTitle ?? document.title :参数可选的兜底写法,两种用法都支持------useTitle() 纯托管、useTitle('xxx') 带初始值
  2. immediate: true:保证"进来那一刻"标题就被同步(比如接管了一个已经乱七八糟的标题)
  3. 返回 ref 而不是字符串 :返回 ref 调用方才能改、页面才响应------组合式函数返回的一律是"活的数据"

【截图位置:点击按钮后浏览器标签页标题变化的效果】

三、useToggle:布尔开关 + 任意两值切换

目标 API :const [state, toggle] = useToggle()------数组解构的返回形式(和 012 篇 toRefs 的对象形式并列,两种都要会)。

js 复制代码
// src/composables/useToggle.js
import { ref } from 'vue'

export function useToggle(init = false) {
  const state = ref(init)

  /**
   * 切换状态
   * @param {boolean} [value] 可选:直接指定目标值
   */
  function toggle(value) {
    // 传了值就用传的,没传就取反
    state.value = value ?? !state.value
  }

  return [state, toggle]
}
vue 复制代码
<template>
  <!-- 场景 1:开关弹窗 -->
  <button @click="toggle()">弹窗开关:{{ open ? '开' : '关' }}</button>

  <!-- 场景 2:明文/密文切换,直接指定值 -->
  <input :type="showPwd ? 'text' : 'password'" v-model="pwd" />
  <button @click="toggle(true)">显示明文</button>
  <button @click="toggle(false)">隐藏</button>
</template>

<script setup>
import { useToggle } from '@/composables/useToggle'

const [open, toggle] = useToggle()          // 弹窗开关
const [showPwd, setPwdVisible] = useToggle(false)  // 密码可见性
const pwd = ref('')
</script>

设计复盘:

  1. 返回数组 [state, toggle] :调用方解构时可以随意重命名(toggle / setPwdVisible),数组按位置对号,特别适合"一数据一方法"的小函数
  2. value ?? !state.value:toggle() 无参取反、toggle(true) 定点设置,一个函数两种用法
  3. JSDoc 注释:写上参数说明,同事用你的函数时 IDE 有提示------协作从注释开始

对象形式返回(项目里统一风格时用):

js 复制代码
// 同一逻辑的另一种返回形态
return { state, toggle }
// 使用:const { state, toggle } = useToggle()

数组形式 适合"元素少且位置固定";对象形式适合成员多、要按名取用的场景。VueUse 两种混用(简单函数用数组,复杂的用对象),你的项目定死一种即可。

四、进阶:把两个函数组合起来

组合式函数的精髓是可以互相组合。写一个 useDisclosure(弹窗控制器),内部用 useToggle:

js 复制代码
// src/composables/useDisclosure.js
import { useToggle } from './useToggle'

/**
 * 弹窗/抽屉控制器
 * @returns {{ isOpen: import('vue').Ref<boolean>, open: Function, close: Function, toggle: Function }}
 */
export function useDisclosure(init = false) {
  const [isOpen, rawToggle] = useToggle(init)

  return {
    isOpen,
    open: () => rawToggle(true),
    close: () => rawToggle(false),
    toggle: () => rawToggle()
  }
}
vue 复制代码
<template>
  <button @click="open">打开确认弹窗</button>

  <div v-if="isOpen" class="mask" @click.self="close">
    <div class="box">
      确定要删除吗?
      <button @click="close">取消</button>
      <button @click="onConfirm">确定</button>
    </div>
  </div>
</template>

<script setup>
import { useDisclosure } from '@/composables/useDisclosure'

const { isOpen, open, close } = useDisclosure()

function onConfirm() {
  console.log('执行删除')
  close()
}
</script>

<style scoped>
.mask { position: fixed; inset: 0; background: rgba(0,0,0,.5);
  display: flex; align-items: center; justify-content: center; }
.box { background: #fff; padding: 20px; border-radius: 8px; }
</style>

open / close / toggle 语义分明的弹窗控制器,一个项目里每个弹窗都用它------这就是 031 篇 Teleport 弹窗的官方搭档。函数调用函数,能力不断叠加,这就是"组合式"三个字的含义。

【截图位置:弹窗打开、点遮罩关闭的运行效果】

五、踩坑记录

  1. useTitle 在 SSR/无 window 环境报错 :document 不存在------需要兼容 SSR 时加 typeof document !== 'undefined' 判断(本系列走 SPA,先知道有这回事)
  2. 返回了死值 :return title.value 返回的是当时的字符串,调用方怎么改页面都不动------永远返回 ref
  3. toggle 里的坑 :value ?? !state.value 若写成 value || !state.value,传 false 会被 || 当 falsy 吞掉变成取反------nullish 合并符(??)才区分 false 和空
  4. 忘记可选括号 :useToggle 定义成 function useToggle(init) 但调用写 useToggle()------init 是 undefined,ref(undefined) 的布尔判断是 false,恰好能用但类型不干净,养成 init = false 给默认值的习惯
  5. 一个文件塞十个函数:useTitle 和 useToggle 混在一个文件里 export------一个函数一个文件,index.js 统一导出,按需 import

六、今日小结

  • useTitle:ref + watch(immediate) 三件套,返回活 ref 是铁律
  • useToggle:[state, toggle] 数组返回便于重命名;value ?? !value 兼容定点设置
  • 返回形态二选一:数组(小而固定)/ 对象(成员多),项目内统一
  • useDisclosure 示范函数组合函数------"组合式"的真正含义

下篇预告

继续造下一组:useMouse / useWindowSize------第一次在组合式函数里操作 window 事件监听,生命周期清理的模板写法会固化成肌肉记忆。下一篇 037。

相关推荐
定栓1 小时前
Vite-legacy产物console删除机制
前端·javascript·vue.js
康小汪9 小时前
AI 写 Vue 总写成同一个味道:给它装 Skills,装的不是知识是边界
前端·vue.js·人工智能·ai编程·最佳实践
北冥有鱼13 小时前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js
apollo_qwe16 小时前
大模型 API 接通了,AI 聊天页面为什么还是不好用?
前端·vue.js·人工智能
小郎君65821 小时前
keep-alive 页面怎么“真刷新”?别改外层 key——一个 refreshVersion 的完整实现
vue.js·算法
徐小夕1 天前
JitWord 4.3 发布!Word 内嵌 Visio 图表、原生流程图一键无损解析
前端·vue.js·github
king王一帅1 天前
做了三年 ProseMirror 后,我用四个月自研排版引擎实现 WPS / Google Docs 替代品,整套框架 MIT 开源
前端·vue.js·开源
仰望.1 天前
vue 表格组件 vxe-table 树表格懒加载支持拖拽排序的详细用法
前端·javascript·vue.js·vxe-table
康小旺2 天前
AI 写 Vue 总写成同一个味道:给它装 Skills,装的不是知识是边界
vue.js·typescript
徐小夕2 天前
JitKnow AI知识库 4.7 发布|多文档推理+拖拽 Agent + 技能库,打造更懂业务的 AI 知识中台
前端·vue.js·github