Vue3 零基础每日笔记(035):什么是 Composables——mixin 之死与逻辑复用新方案

一、前言

组件篇结束,新阶段开始:组合式函数(Composables)篇。本篇先回答"为什么需要它":Vue2 时代的逻辑复用方案 mixin 为什么被淘汰?Composables 凭什么成为官方钦定的答案?012 篇埋的"toRefs 在第五阶段天天见"也从这篇开始兑现。

二、先看老方案 mixin 为什么凉了

需求:计数逻辑(count + increment)要在三个页面复用。Vue2 的 mixin 写法:

js 复制代码
// src/mixins/counterMixin.js
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
vue 复制代码
<!-- 页面里混入 -->
<script>
import counterMixin from '@/mixins/counterMixin'

export default {
  mixins: [counterMixin],
  mounted() {
    this.increment()   // count 从哪来的?要跑去 mixin 文件查
  }
}
</script>

能跑,但三个致命伤:

  1. 来源不明 :模板里用了 count、increment,但当前文件里搜不到定义------每个都得跳到 mixin 文件里确认,文件多了就是"考古"
  2. 命名冲突 :两个 mixin 都有 increment,后者静默覆盖前者,不报错、难排查
  3. 数据来源错综:多个 mixin 之间还可能互相依赖(A 用了 B 的 data),牵一发动全身

本质问题:mixin 是"把代码复制合并进组件",合并过程黑盒且不可控。

三、Composables:函数级别的逻辑复用

组合式函数的定义简单到离谱:一个使用了 Vue 组合式 API 的普通函数,约定以 use 开头:

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

export function useCounter(init = 0) {
  const count = ref(init)

  function increment() {
    count.value++
  }

  function reset() {
    count.value = init
  }

  // 把响应式数据和方法"打包返回"
  return { count, increment, reset }
}
vue 复制代码
<!-- 页面使用 -->
<template>
  <p>{{ count }}</p>
  <button @click="increment">+1</button>
  <button @click="reset">归零</button>
</template>

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

// 解构即用,来源清清楚楚
const { count, increment, reset } = useCounter()
</script>

对比 mixin 的三大痛,逐条解决:

  1. 来源明确 :count、increment 全部来自 useCounter() 的返回值,一目了然
  2. 无冲突 :名字冲突了会怎样?解构时自己重命名就行------const { count: postCount } = useCounter(),主动权在使用者手里
  3. 可传参、可组合 :useCounter(10) 给初始值;组合式函数内部还能调用别的组合式函数,像搭积木一样组装逻辑

一句话定位:组合式函数把"逻辑"从"组件"里解放出来,成为独立、可测试、可复用的单元。

四、两个马上用得上的心智模型

模型 1:无状态 vs 有状态

js 复制代码
// 有状态:内部有 ref,每次调用创建**独立**的状态
const a = useCounter()  // a.count
const b = useCounter()  // b.count ------ 和 a 互不影响
js 复制代码
// 无状态(工具型):纯函数包装,不持有状态
function useAdd(a, b) {
  return computed(() => a.value + b.value)
}

大部分场景用有状态型(每次调用独立实例);需要全局共享单例时,把状态提到函数外面(模块级变量),那是 Pinia(053 篇)的主场。

模型 2:生命周期钩子可以写在里面

js 复制代码
import { onMounted, onBeforeUnmount } from 'vue'

export function useLog(name) {
  onMounted(() => console.log(`${name} 挂载了`))
  onBeforeUnmount(() => console.log(`${name} 要卸载了`))
}

组合式函数里注册的钩子会自动绑定到调用它的组件实例 上------组件卸载时自动清理。但有个铁律:必须在 setup 的同步执行阶段调用(和 028 篇 inject 同款约束),放进 setTimeout 或事件回调里钩子就绑不上实例了。

五、实战:useStorage------第一个"生产级"组合式函数

一个真正有用的例子:响应式数据 + localStorage 双向同步(010 篇草稿案例的函数化):

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

export function useStorage(key, defaultValue = '') {
  // 初始化:优先读本地缓存
  const data = ref(JSON.parse(localStorage.getItem(key)) ?? defaultValue)

  // 数据变了就写回本地
  watch(data, (val) => {
    localStorage.setItem(key, JSON.stringify(val))
  }, { deep: true })

  return data
}
vue 复制代码
<template>
  <input v-model="draft" placeholder="草稿自动保存,刷新不丢" />
  <p>当前草稿:{{ draft }}</p>
</template>

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

// 一行顶 010 篇的一整段
const draft = useStorage('draft', '')
</script>

对比 010 篇手写的版本:组件里从"reactive + watchEffect + 存取逻辑十几行"压缩成一行 useStorage('draft', '')。复用的复利,从这一刻开始计算。

【截图位置:输入草稿后刷新页面内容保留的效果】

六、踩坑记录

  1. 不用 use 前缀 :能跑,但全团队都会骂你------use 前缀是社区强约定(VueUse 全家都叫 useXxx),看到 useXxx 就知道"这是组合式函数,得在 setup 里同步调用"
  2. 在异步回调里调用组合式函数 :setTimeout(() => { const x = useCounter() }) 内部的生命周期钩子/inject 全部失效------组合式函数只准在 setup 同步阶段调用
  3. 返回值解构丢响应式 :return { count } 返回 ref 时解构没问题(ref 是盒子,012 篇);但如果内部用 reactive 聚合再直接 return,调用方解构就断------对外返回前记得 toRefs(039 篇最佳实践细讲)
  4. 把组合式函数当组件用 :<useCounter /> ❌------它是函数不是标签,作用是"给 setup 提供逻辑"
  5. 文件位置混乱 :约定放 src/composables/(有的项目叫 hooks/),一个函数一个文件,和组件分开

七、今日小结

  • mixin 三宗罪:来源不明、命名冲突、互相纠缠,已被官方边缘化
  • Composables = 以 use 开头、内部用组合式 API、返回响应式数据的函数
  • 有状态型每次调用独立;生命周期钩子可内含,自动绑定调用者实例
  • 铁律:只在 setup 同步阶段调用 ;目录约定 src/composables/

下篇预告

理论齐了,开始动手造。第一组最简单的:useTitle (响应式改网页标题)和 useToggle(布尔开关)------十行代码的组合式函数长什么样?下一篇 036 手把手封装。

相关推荐
陈卫军老师6 小时前
陈卫军语录全集总结:12句话,一条主线
经验分享·笔记
2501_9269783310 小时前
给自指系统接一个外部锚 —— 一个关于「用 AI 观察自己」的方法
人工智能·经验分享·笔记·机器学习·ai写作
AOI小白新手上路14 小时前
anomalib 缺陷检测复现笔记:从跑通库到 EfficientAD 落地
人工智能·笔记·机器学习
xxwl58514 小时前
RabbitMQ 学习笔记
笔记·学习·rabbitmq
加油努力有饭吃15 小时前
DLMS/COSEM 蓝皮书解读(二十六):Auto connect(class_id = 29)—— 自动拨号与主动连接(出站管理)
笔记
CarIise16 小时前
Java实训阶段查漏补缺复习笔记1
java·开发语言·笔记
陈卫军老师16 小时前
陈卫军谈商业底线:营销是术,产品是道
经验分享·笔记·流量运营
智者知已应修善业16 小时前
【51单片机只有一个8*8点阵数据如何滚动起来一片595+一组IO+花样】2022-1-15
c++·经验分享·笔记·单片机·蓝桥杯·硬件架构
Space6553618 小时前
常见组学技术笔记:从 DNA 基因组学到 Multi-omics
笔记
码艺-Alimjan18 小时前
Tauri 网站To桌面应用实战总结
前端·javascript·vue