Vue3 Hooks  对比 Vue2 Mixins 两者之间的区别?

在Vue生态中,"Hooks"这个概念随着Vue3的发布而变得火热。但很多开发者可能会有疑问:Vue2中不是也有"混入"(mixins)吗?它们有什么区别?为什么Vue3要引入Hooks?

本文将深入探讨Vue3中Hooks的实现方式,并与Vue2的Mixins进行对比,通过一个实际的例子,让你直观地理解这场"逻辑复用革命"的本质。

一、啥是 Hooks?

Hooks 就是一个带状态的函数。

啥意思?就是普通函数只管算东西,算完就完事了。但 Hooks 不一样,它内部可以有自己的"记忆"(响应式数据),还能在组件卸载的时候自己打扫卫生(清理定时器啥的)。

打个比方:

  • 普通函数就像一次性筷子,用完就扔

  • Hooks 就像能反复使用的餐具,每次用都能记住上次的状态

二、拿个真实场景说话

假设我们要做个登录页面,有个"发送验证码"的按钮,点完之后要倒计时60秒,这60秒内按钮是灰色的点不了,倒计时结束按钮才能再点。

这个功能在很多地方都会用到,比如注册、找回密码等等,所以我们肯定想把它抽出来复用。

三、看看 Vue2 咋搞的(Mixins 版本)

先写个 Mixin

// countdownMixin.js
export default {data() {return {countdownSeconds: 60,  // 倒计时秒数isCounting: false,     // 是否正在倒计时timerId: null,         // 定时器放这儿buttonText: '发送验证码' // 按钮上显示的字}},methods: {startCountdown() {this.isCounting = truethis.buttonText = `${this.countdownSeconds}秒后重发`this.timerId = setInterval(() => {this.countdownSeconds--if (this.countdownSeconds <= 0) {// 倒计时结束clearInterval(this.timerId)this.isCounting = falsethis.buttonText = '重新发送'this.countdownSeconds = 60} else {this.buttonText = `${this.countdownSeconds}秒后重发`}}, 1000)}},// 组件销毁时清理定时器,防止内存泄漏beforeDestroy() {if (this.timerId) {clearInterval(this.timerId)}}
}

  

在组件里用这个 Mixin

vue2
<template><div><input type="text" v-model="phone" placeholder="手机号" /><button @click="sendCode" :disabled="isCounting">{{ buttonText }}</button></div>
</template><script>
import countdownMixin from './countdownMixin'export default {mixins: [countdownMixin],  // 引入mixindata() {return {phone: '',}},methods: {sendCode() {console.log('发送验证码到', this.phone)this.startCountdown()}}
}
</script>

  

这样写有啥问题?

问题1:数据从哪来的?
新来的同事看到代码里的 isCountingbuttonText,肯定一脸懵:这变量哪冒出来的?得去翻 mixin 文件才能知道。

问题2:命名冲突
假如我组件里本来就有个 isCounting 变量,想记录别的状态,完蛋,跟 mixin 里的冲突了。谁后加载谁覆盖,bug 就偷偷出现了。

问题3:方法被覆盖
要是不小心在组件里也写了个 startCountdown 方法,mixin 里的就被覆盖了,倒计时功能就失效了。

问题4:不好找 bug
倒计时出问题了,你得去 mixin 文件里找原因,但 mixin 可能被十几个地方用着,改起来提心吊胆。

四、再看看 Vue3 咋搞的(Hooks 版本)

先写个 Hook

javascript
// useCountdown.js
import { ref, computed, onUnmounted } from 'vue'export function useCountdown(initialSeconds = 60) {// 所有的状态都写在里面const seconds = ref(initialSeconds)  // 当前剩余秒数const isActive = ref(false)          // 是否正在倒计时let timer = null                      // 定时器// 按钮上显示的文字(计算属性)const buttonText = computed(() => {if (isActive.value) {return `${seconds.value}秒后重发`}return '发送验证码'})// 开始倒计时const start = () => {if (isActive.value) returnisActive.value = truetimer = setInterval(() => {if (seconds.value > 0) {seconds.value--} else {// 倒计时结束clearInterval(timer)isActive.value = falseseconds.value = initialSeconds}}, 1000)}// 组件卸载时自动清理(不用你操心)onUnmounted(() => {if (timer) {clearInterval(timer)}})// 明确告诉你:我只返回这些东西return {seconds,isActive,buttonText,start}
}

  

在组件里用这个 Hook

vue3
<template><div><input type="text" v-model="phone" placeholder="手机号" /><button @click="sendCode" :disabled="isActive">{{ buttonText }}</button></div>
</template><script setup>
import { ref } from 'vue'
import { useCountdown } from './useCountdown'// 组件自己的数据
const phone = ref('')// 使用倒计时功能
// 这里清清楚楚写着:我要用 isActive、buttonText、start
const { isActive, buttonText, start } = useCountdown(60)// 可以自己定义一个 isCounting,完全不冲突!
const isCounting = ref(false)  // 这是我自己的状态,跟倒计时没关系const sendCode = () => {console.log('发送验证码到', phone.value)start()  // 开始倒计时
}// 如果需要用两个倒计时?完全没问题!
const code1 = useCountdown(60)   // 手机验证码倒计时
const code2 = useCountdown(120)  // 邮箱验证码倒计时,互不干扰
</script>

  

这样写有啥好处?

好处1:代码出处一目了然
看到 isActive 就知道是从 useCountdown 来的,不用到处找。

好处2:随便命名不怕冲突
想叫啥就叫啥,还可以重命名:

javascript
const { isActive: codeActive, buttonText: codeBtnText } = useCountdown()

好处3:按需取用
只要 isActive 和 buttonText,别的不要?那就别解构出来,完美。

好处4:多个实例独立
想用几个倒计时就用几个,各自管各自的,谁也不影响谁。

五、一句话总结区别

 
对比MixinsHooks
数据来源 神神秘秘,不知道哪来的 明明白白,从函数返回的
命名 容易打架 随便取,不怕冲突
复用 牵一发可能动全身 各用各的,互不干扰
调试 得翻好几个文件 就在眼前,好找