hooks,mixin,pinia,vuex

在 Vue 3 中,自定义 Hooks(也常被称为组合式函数)是封装和复用逻辑的终极利器。它的本质就是一个普通的 JavaScript/TypeScript 函数,利用 Vue 3 的组合式 API(如 ref, reactive, watch, 生命周期钩子等)将特定的业务逻辑抽离出来。

学会自定义 Hooks,能让你的代码从"面条式"变得极其清爽、高内聚且易于维护。下面为你详细拆解它的使用步骤和最佳实践:

🛠️ 第一步:创建自定义 Hook

按照社区约定,Hook 的文件名和函数名通常以 use 开头。

场景举例:假设我们需要在多个页面获取鼠标当前的位置。我们可以封装一个 useMouse 的 Hook。

// hooks/useMouse.js

import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {

// 1. 定义响应式状态

const x = ref(0)

const y = ref(0)

// 2. 定义处理逻辑

const updateMouse = (e) => {

x.value = e.pageX

y.value = e.pageY

}

// 3. 绑定生命周期(自动处理副作用)

onMounted(() => {

window.addEventListener('mousemove', updateMouse)

})

onUnmounted(() => {

window.removeEventListener('mousemove', updateMouse)

})

// 4. 将需要暴露给组件的状态或方法 return 出去

return { x, y }

}

📦 第二步:在组件中使用自定义 Hook

在 Vue 组件的 <script setup> 中,你可以像导入普通函数一样导入并使用它。

<template>

<div>

当前鼠标位置:X = {{ x }}, Y = {{ y }}

</div>

</template>

<script setup>

import { useMouse } from '@/hooks/useMouse'

// 直接解构获取 Hook 中暴露的响应式数据

const { x, y } = useMouse()

</script>

⚙️ 第三步:进阶用法(支持传参与方法暴露)

Hook 的强大之处在于它可以接收参数,并返回方法,从而实现高度灵活的逻辑复用。

场景举例:封装一个带初始值和步长的计数器 useCounter。

// hooks/useCounter.js

import { ref, computed } from 'vue'

// 接收外部传入的初始值和步长

export function useCounter(initialValue = 0, step = 1) {

const count = ref(initialValue)

const increment = () => {

count.value += step

}

const decrement = () => {

count.value -= step

}

// 也可以返回计算属性

const doubleCount = computed(() => count.value * 2)

return {

count,

doubleCount,

increment,

decrement

}

}

在组件中使用:

<script setup>

import { useCounter } from '@/hooks/useCounter'

// 传入初始值 10,步长 2

const { count, doubleCount, increment } = useCounter(10, 2)

</script>

💡 为什么要用自定义 Hooks?(核心优势)

  1. 逻辑复用:把多个组件共用的逻辑(如网络请求、表单验证、本地存储同步等)抽离出来,避免代码重复。

  2. 代码解耦与高内聚:在 Vue 2 的 Options API 中,一个功能的逻辑往往被拆分在 data、methods、watch 里。而使用 Hooks,一个功能的所有变量、方法、监听都能聚合在一起,代码阅读和维护极其方便。

  3. 更好的 TypeScript 支持:相比 Vue 2 的 Mixin,自定义 Hooks 对 TS 的类型推导非常友好。

⚠️ 避坑指南与最佳实践

  1. 命名规范:务必以 use 开头(如 useFetch, useLocalStorage),这是 Vue 社区识别组合式函数的标志。

  2. 单一职责:一个 Hook 最好只解决一个特定的问题,保持逻辑纯粹。

  3. 生命周期管理:如果在 Hook 中添加了全局的事件监听或定时器,记得在 onUnmounted 中清理它们,防止内存泄漏。

  4. 不要无脑依赖 this:自定义 Hooks 是独立的函数作用域,完全抛弃了 Vue 2 那种依赖 this 的思维,所有变量都是独立声明的。

如果你在日常开发中遇到一些通用需求(比如防抖节流、监听元素尺寸、暗黑模式切换等),其实不必自己从头写,强烈推荐你去了解一下 Vue 官方团队维护的 VueUse 库,它内置了 200+ 个开箱即用的高质量 Hooks,能极大提升你的开发效率。

Pinia 和 Vuex 在用法上的核心区别,主要体现在概念更精简、架构更扁平、以及对 TypeScript 更友好这三个方面。

简单来说,Pinia 抛弃了 Vuex 中繁琐且容易混淆的 mutations,让状态管理变得非常直观。下面为你详细拆解它们在具体用法上的不同:

  1. 核心概念的简化(去掉了 Mutations)

* Vuex:修改状态必须走一套固定的"繁琐流程"。同步修改必须通过 mutations,异步操作必须通过 actions 提交 mutations。

* 流程:组件 -> dispatch actions -> commit mutations -> 修改 state。

* Pinia:彻底移除了 mutations。无论是同步还是异步操作,统一都在 actions 中处理,甚至可以直接在组件中修改 state。

* 流程:组件 -> 调用 actions 或直接修改 -> 修改 state。

  1. 架构设计的区别(扁平化 vs 嵌套模块化)

* Vuex:采用"单一状态树",所有模块都挤在一个大 Store 里,通过 modules 进行层层嵌套。在大型项目中,访问数据时路径会很长(例如 store.state.user.profile.name),且需要配置 namespaced: true 来避免命名冲突。

* Pinia:采用"扁平化架构"。每一个 Store 都是独立定义、按需引入的模块(例如 useUserStore、useCartStore)。它们之间是平等的,没有嵌套关系,引用路径非常清晰。

  1. 代码写法直观对比

假设我们要实现一个简单的"异步增加计数器"的功能:

Vuex 的写法(需要分三步走):

// 1. 在 state 中定义数据

state: { count: 0 },

// 2. 在 mutations 中写同步修改逻辑

mutations: {

INCREMENT(state, num) { state.count += num }

},

// 3. 在 actions 中写异步逻辑并提交 mutation

actions: {

incrementAsync({ commit }, num) {

setTimeout(() => { commit('INCREMENT', num) }, 1000)

}

}

在组件中使用: this.$store.dispatch('incrementAsync', 1)

Pinia 的写法(一步到位):

export const useCounterStore = defineStore('counter', {

state: () => ({ count: 0 }),

actions: {

// 直接在 action 里写异步逻辑并修改 state

async incrementAsync(num) {

setTimeout(() => { this.count += num }, 1000)

}

}

})

在组件中使用:

const counterStore = useCounterStore()

counterStore.incrementAsync(1) // 或者直接在组件里写 counterStore.count++

  1. TypeScript 的支持度

* Vuex:对 TS 的支持比较弱,通常需要手动声明大量的类型接口(如 RootState、ActionContext 等),写起来非常繁琐,代码提示也经常失效。

* Pinia:原生为 TS 设计。它能根据你定义的 state、getters 和 actions 自动推导类型,无需额外声明,就能获得完美的代码自动补全和类型检查。

总结对比表

为了让你更直观地看清区别,可以参考下表:

对比维度 Vuex Pinia

核心概念 State, Getters, Mutations, Actions, Modules State, Getters, Actions (无 Mutations)

修改状态 必须通过同步的 mutations 直接在 actions 或组件中修改

模块化 嵌套结构,需配置命名空间 扁平化,每个 Store 独立且按需引入

TS 支持 较弱,需大量手动声明类型 完美,全自动类型推导

异步处理 只能在 actions 中处理 actions 同时支持同步和异步

如果你正在学习或开启一个新的 Vue 3 项目,强烈建议直接上手 Pinia,它的用法更符合现代前端开发的直觉,开发体验也会好很多。

Pinia 的 Store 和 Vue 的 Hooks(组合式函数)完全不一样。虽然它们看起来很像(都是用 useXxx 开头,写法也接近),但它们的核心作用有着本质的区别。

简单来说:Pinia Store 是为了"全局共享状态",而 Hooks 是为了"复用逻辑代码"。

为了让你更直观地理解,我们可以从以下几个维度来对比:

🎯 核心区别对比

对比维度 Pinia Store (仓库) Hooks (组合式函数)

设计目的 全局状态管理,让多个组件共享同一份数据。 逻辑复用,把一段通用的业务逻辑抽离出来。

实例数量 全局单例。无论你在多少个组件里调用,拿到的都是同一份数据。 独立实例。每次在组件里调用,都会创建一份全新的、独立的数据。

生命周期 与应用绑定,持久存在(除非手动销毁)。 随组件创建而创建,随组件销毁而销毁。

典型场景 用户登录信息、全局主题色、购物车数据。 表单验证逻辑、鼠标位置追踪、倒计时功能。

💡 举个生动的例子

* Pinia Store 就像"云盘共享文件夹":

你在 A 页面(组件)里往文件夹里放了一张照片,B 页面(组件)里立刻就能看到这张照片。大家操作的都是云端同一份文件。

* Hooks 就像"本地打印出来的文件":

你在 A 页面调用了这个 Hook,就像打印了一份文件在桌面上涂涂改改;你在 B 页面再调用一次,就像又去打印机打了一份全新的。A 页面的修改绝对不会影响到 B 页面的那份文件。

📝 代码直观感受

  1. Pinia Store(数据是共享的)

// stores/counter.js

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {

state: () => ({ count: 0 })

})

// 组件 A

const storeA = useCounterStore()

storeA.count++ // 修改为 1

// 组件 B

const storeB = useCounterStore()

console.log(storeB.count) // 打印出 1!(因为 A 和 B 共享同一份 state)

  1. Hooks(数据是独立的)

// hooks/useCounter.js

import { ref } from 'vue'

export function useCounter() {

const count = ref(0) // 每次调用都会生成一个新的 ref(0)

return { count }

}

// 组件 A

const { count: countA } = useCounter()

countA.value++ // 修改为 1

// 组件 B

const { count: countB } = useCounter()

console.log(countB.value) // 打印出 0!(因为 B 拿到的是自己独立的一份 count)

🤝 它们可以结合使用吗?

当然可以,而且非常推荐!

Pinia 的 Store 完美支持使用 Hooks(Composition API)的写法来定义(也就是 setup 语法)。这意味着你可以把 Hooks 强大的逻辑复用能力,直接融入到 Pinia 的全局状态管理中,实现 1+1 > 2 的效果。

总结: 如果你需要跨组件共享数据(比如用户信息),请用 Pinia Store;如果你只是想把一段通用的 JS/Vue 逻辑(比如获取鼠标坐标)抽离出来让代码更整洁,请用 Hooks

插件里的mutation.

简单来说,Pinia 在代码层面确实彻底去掉了 Mutation,但在 Vue DevTools 的 Timeline(时间线)中看到的 mutation,指的是 Vue 3 底层的"响应式变更事件",而不是 Vuex 里的那个 Mutation。

为了让你彻底明白,我们可以从以下两个层面来拆解:

  1. 代码层面:Pinia 确实没有 Mutation

在写代码时,Pinia 彻底抛弃了 Vuex 那种"必须通过 commit 提交 mutation 才能修改状态"的繁琐流程。

* 在 Vuex 中:修改状态必须写一个同步的 mutations: { SET_XXX(state, val) { ... } }。

* 在 Pinia 中:你可以直接在 actions 里修改 this.xxx,或者在组件里直接 store.xxx = 1。Pinia 内部并没有让你去定义一个叫 mutation 的东西。

  1. 调试层面:DevTools 里的 mutation 是什么?

你在 Vue DevTools 的 Timeline 里看到的那些 mutation 记录,其实是 Vue 3 响应式系统底层的"副作用触发记录"。

* 底层原理:Pinia 的状态(state)本质上就是 Vue 3 的一个 reactive 响应式对象。当你修改 store.count++ 时,表面上看是直接修改,但实际上 Pinia 内部是通过 $patch 方法,并利用 Vue 的响应式拦截(Proxy)来完成的。

* DevTools 的记录:Vue DevTools 的 Timeline 功能非常底层,它会监听整个 Vue 应用中所有响应式数据的变化。当你修改 Pinia 的状态时,Vue 的响应式系统会触发一个底层的"set"或"mutation"操作(比如 events: debuggerEvents,type: MutationType.direct),DevTools 捕捉到了这个底层动作,并把它展示在了时间线上。

打个通俗的比喻:

* Vuex 的 Mutation 就像是公司的"正式公文审批流程"。你想改个数据,必须填个单子(commit),领导签字(mutation)后才能改。

* Pinia 的修改 就像是你"直接动手改数据"。

* DevTools 里的 mutation 就像是办公室里的"高清监控摄像头"。虽然你是直接动手改的(没用公文审批),但监控摄像头(DevTools)依然记录下了"某某在几点几分修改了这份文件"的动作画面。这个监控记录的名字叫 mutation(变更),但它绝不是 Vuex 里的那个"公文审批流程"。

总结

你在 Timeline 里看到的,是 Pinia 贴心地帮你把每一次状态修改的"监控录像"都自动记录下来了。

这也正是 Pinia 相比老版本 Vuex 强大的地方:在 Vuex 中,如果你不通过 commit 提交 mutation,DevTools 是无法追踪到状态变化的;而 Pinia 无论你是在 action 里改,还是在组件里直接改,都能被 DevTools 完美捕捉并支持"时间旅行"调试。

相关推荐
Shirley~~8 小时前
npm包发布与 dist-tag 管理指南
前端·npm·node.js
Csvn8 小时前
前端可视化入门:Canvas、SVG 与 D3.js 基础
前端·d3.js
bug-100868 小时前
vue2和vue3的路由变化
前端·vue.js
百数平台8 小时前
功能更新——百数详情页“数据简报”与“关联标签页”配置指南
java·服务器·前端
Csvn8 小时前
前端技术 - 3D 图形基础
前端·d3.js
狼丶宇先森8 小时前
vue-sign-canvas v2 重构复盘:从 Vue 2 签名板到 Vue 3 + TypeScript 组件库
前端·vue.js·重构·typescript·开源软件·canvas
迁旭8 小时前
Claude Code 项目 /init 命令详解
前端·javascript·chrome·机器学习·语言模型·gpt-3
ZC跨境爬虫8 小时前
跟着 MDN 学CSS day_9:(深入掌握CSS选择器核心技能测试)
前端·css·ui·html
Daybreak8 小时前
Convex + Next.js + Clerk 上线求生指南:六个坑,一个比一个离谱
前端