父传子
defineProps
// 父组件
<template>
<ChildrenOne :state="state" :parentAttrs="parentAttrs" />
</template>
<script setup>
import { ref } from 'vue'
import ChildrenOne from './components/Children.vue' // 引入子组件
const state = ref(0)
const parentAttrs = () => { }
</script>// 子组件
<template>
<div>{{ state }}</div>
</template>
<script setup>
import { defineProps, ref } from 'vue'
const props = defineProps({
state: Number,
parentAttrs: Function
})
const { state, parentAttrs } = toRefs(props) // 将父组件变量及事件从 props 解构出来
console.log('获取父组件变量:' + state)
parentAttrs() // 使用父组件事件
</script>useAttrs
函数可以接收父组件传递的属性和事件,在组件中可以直接使用这些属性和事件
当 useAttrs 和 defineProps 一起使用时,defineProps 优先级会更高
// 父组件
<template>
<ChildrenThree :state="state" @click="parentAttrs" />
</template>
<script setup>
import { ref } from 'vue'
import ChildrenOne from './components/Children.vue' // 引入子组件
const state = ref(0)
const parentAttrs = () => { }
</script>// 子组件
<template>
<div>
<h2>ChildrenThree</h2>
<button @click="parentAttrs">触发父组件 useAttrs 的事件</button>
</div>
</template>
<script setup>
import { useAttrs } from 'vue'
const { state, parentAttrs } = useAttrs() // 获取父组件变量及事件
console.log('获取父组件变量:' + state)
parentAttrs() // 使用父组件事件
</script>子传父
defineEmits
// 子组件
<template>
<button @click="myClick">子传父</button>
<!-- <button @click="$emit('child-event', data)">子传父</button> -->
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const state = ref(0)
const emit = defineEmits(['toParentOnt'])
const myClick = () => {
emit('childAttrs', state.value)
}
</script>// 父组件
<template>
<ChildrenTwo @childAttrs="childAttrs"/>
</template>
<script setup>
import ChildrenTwo from './components/Children.vue' // 引入子组件
const childAttrs = (msg) => {
console.log('获取子组件变量:', msg)
}
</script>ref
父组件主动获取子组件内部变量或触发事件
// 父组件
<template>
<button @click="childRef">获取子组件数据和方法</button>
<ChildrenOne ref="comp"/>
</template>
<script setup>
import ChildrenOne from './components/Children.vue' // 引入子组件
const comp = ref(null)
const childRef = () => {
console.log('获取子组件变量:' + comp.value.count)
comp.value.countFun() // 使用子组件事件
}
</script>// 子组件
<script setup>
import { ref, defineExpose } from 'vue'
const state = ref(0)
defineExpose({
count: state.value,
countFun () {
console.log('这是子组件的方法')
}
})
</script>祖先传后代
provide:用于定义要提供给后代组件的数据或方法
inject:用于在后代组件中接收从祖先组件提供的数据或方法
// 祖先
<script setup>
import { ref, provide } from 'vue'
const state = ref(0)
provide('state', state.value)
</script>// 后代
<script setup>
import { inject } from 'vue'
console.log('获取祖先组件变量:' + inject('state'))
</script>兄弟组件传递
该方法没有实践过,姑且记录
// 共用父组件
<script setup>
import Vue from 'vue'
export const EventBus = new Vue()
</script>// 子组件 (发送)
<script setup>
EventBus.$emit('event-name', data)
</script>// 子组件 (接收)
<script setup>
EventBus.$on('event-name', (data) => {
console.log(data)
})
</script>Mitt、Pinia 和 Vuex
以下对比说明来自“豆包”
核心维度对比
| 特性 | Vuex | Pinia | Mitt |
|---|---|---|---|
| 核心定位 | Vue 2 官方状态管理库,集中式管理应用状态 | Vue 3 官方推荐状态管理库,极简版状态管理 | 通用事件总线(无框架绑定),仅做事件发布 / 订阅 |
| 框架依赖 | 仅支持 Vue(2/3 对应不同版本) | 主推 Vue 3,兼容 Vue 2 | 无框架依赖(Vue/React/ 原生 JS 都能用) |
| 核心能力 | 集中存储 + 修改状态,强分层规范 | 集中存储 + 修改状态,极简灵活 | 仅触发 / 监听事件,不存储状态 |
| 数据流向 | 单向数据流(State→View→Mutation/Action→State) | 单向数据流,简化分层 | 无固定数据流(事件触发后逻辑自定) |
| API 复杂度 | 高(State/Getter/Mutation/Action/Module) | 低(State/Getter/Action,无 Mutation) | 极低(仅 on/emit/off/clear 4 个核心 API) |
| TypeScript 支持 | 支持但繁琐(需手动声明类型) | 原生支持,自动类型推断 | 支持(极简类型声明) |
| DevTools 调试 | 支持,但模块嵌套调试复杂 | 完全支持,调试更清晰(支持时间旅行) | |
| 热更新 | 需要额外配置 | 原生支持,无需配置 | |
| 代码分割 | 不支持(打包时全量引入) | 支持(按使用自动分割代码) | |
| 状态重置 | 需手动实现 | 内置 $reset() 方法 | |
| 代码冗余度 | 高(mutation 冗余) | 低(极简语法) | |
| 体积 | 约 10KB(打包后) | 约 3KB(打包后) | 约 200B(超轻量) |
关键使用场景
用 Mitt 的场景
- 小项目中简单的组件通信(比如兄弟组件、跨层级组件传值 / 触发操作);
- 非 Vue 场景的事件通信(比如原生 JS 项目、React 项目);
- 仅需要「触发操作」,无需「存储状态」的场景(比如通知某个组件刷新)。
用 Vuex 的场景
- 维护 Vue 2 老项目(Vuex 是 Vue 2 生态的标配);
- 团队已深度熟悉 Vuex,且项目状态逻辑简单,无需迁移;
- 需要严格的「同步 / 异步分离」规范(Mutation 强制同步)。
用 Pinia 的场景
- 新的 Vue 3 项目(官方首选,语法更贴合 Composition API);
- 需要强 TypeScript 支持的 Vue 项目(Pinia 类型推断更丝滑);
- 状态管理逻辑复杂,希望代码更简洁(无 Mutation 冗余);
- 希望状态模块天然隔离(无需手动配置命名空间)。
Mitt 使用
src 同级新建 mtti 文件夹,并在内部创建 index.js
// mtti/index.js
import mitt from "mitt"
const mitter = mitt()
export default mitter之后在组件中通过 mitter.emit 发送 、mitter.on 接受即可
// 子组件 (发送)
<script setup>
import mitter from "../mitt" // 引入 mitt
import { reactive } from "vue"
const state = reactive({ count: 0 })
const addCount = () => {
state.count++
mitter.emit("addCount", state.count) // 参数一:总线事件的名称、参数二:需要传递的参数
}
</script>// 子组件 (接收)
<script setup>
import mitter from "../mitt" // 引入 mitt
import { reactive, onUnmounted } from "vue"
const count = reactive(0)
// 【注意】如果不进行注销,则会多次触发事件
onUnmounted(() => {
mitter.off('addCount')
})
mitter.on('addCount', msg => { // 参数一:总线事件的名称、参数二:回调函数
count = msg
})
</script>Vuex 使用(Vue 2.0)
全局引用 Vuex
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store' // 全局引用
app.use(store).mount('#app')在 src 同级新建 store 文件夹,并在内部创建 index.js
其中包含 state、getters、mutations、actions 对象
import { createStore } from 'vuex'
export default createStore({
// state 用于存储原始数据
state: {
user: ''
},
// getters 用于存储前端获取 state 数据的方法
getters: {
getUser (state) { // state 以参数的形式返回给前端
return state.user // 前端使用 【store.getters.getUser】 获取数据状态
}
},
// Vuex 明确规定:mutations 【必须是同步操作函数】,不能包含任何异步逻辑(比如 setTimeout、axios 请求、Promise 等)
// 它是唯一被允许直接修改 state 的 “入口”,目的是让每一次 state 的变更都能被精准追踪,如果在 mutations 中放入异步函数,state 的修改时机就会变得不可控,比如:异步操作延迟 1 秒执行,这 1 秒内你无法确定 state 到底是 “修改前” 还是 “修改后” 的状态
mutations: {
updataUser(state, user){ // 前端使用 store.commit('updataUser', '修改后的 state.user 的数据')
state.user = user;
}
},
// actions 存放异步操作函数
// 它无法直接修改 state,需要通过在 mutations 定义的函数触发更新
actions: {
login: (context, data) => { // 前端使用 store.dispatch("login", {}) 触发异步更新
$.ajax({
url: '',
type: 'POST',
success(resp) {
context.commit('updataUser', resp.access) // 触发同步方法以更新 state.user
}
})
}
}
})页面使用
import { useStore } from 'vuex'
const store = useStore()
console.log(store.getters.getUser) // 获取数据
store.commit('updataUser', '修改后的 state.user 的数据') // 同步修改数据
store.dispatch('login') // 异步修改数据mapState、mapGetters、mapMutations、mapActions 四个辅助函数
mapState/mapGetters映射到组件的computed(计算属性),用于 读取 Vuex 中的状态;mapMutations/mapActions映射到组件的methods(方法),用于 修改 Vuex 中的状态(前者同步、后者异步);
mapState
在实际使用中如果想引用多个值则需要一一引入很不方便,此时需要使用 mapState 将 state 值映射为实例的 computed 属性
这里映射的是 store 中的原始 state
<template>
<div>{{ count }} - {{ myName }}</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
// 方式1:数组写法(组件属性名 = state名)
...mapState(['user', ……]), // 映射 this.user 为 store.state.user 的值
// 写法等价于:
// user() { return this.$store.state.user }
// 方式2:对象写法(自定义属性名/组合多个state)
// 我们映射的时候,不想用原有的属性名,那就可以像下面这样做,用对象的形式去别名
...mapState({
myuser: state => state.user, // 映射 this.myuser 为 store.state.user 的值
……
})
}
}
</script>mapGetters 、mapMutations 和 mutations
函数的用法同 mapState ,但它们各自的适用场景不同,区别在于它们取值来源不同
mapGetters 取自 store.getters
mapMutations 取自 store.mutations
mutations 取自 store.actions
这里的取值皆为原始数据 store.state 加工后的值
分割模块(Module)
Vuex 允许我们将 store 分割成模块(Module),每个模块拥有自己的 state、getters、mutation、action
// 文件目录结构如下
-store
-index.js
-modules
-app.js // 第一个分割模块,拥有自己独立的 state、getters、mutation、action
-bus.js // 第二个分割模块,拥有自己独立的 state、getters、mutation、action// index.js
import Vue from 'vue'
import Vuex from 'vuex'
/**
* 方式一:手动引入模块
import bus from './modules/bus'
import app from './modules/app'
Vue.use(Vuex)
let store = new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
namespaced: true,
app,
bus
}
});
*/
/**
* 方式二:动态引入模块
const path = require('path')
const files = require.context('./modules', false, /\.js$/)
let modules = {}
files.keys().forEach(key => {
let name = path.basename(key, '.js')
modules[name] = files(key).default || files(key)
})
let store = new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules
});
*/
export default store使用增加相应映射路径即可
// vue
this.$store.commit('app/setUser', {name: '张三'}); // 修改 app 模块中的数据
// import store from '@/store'
// let curUser = store.app.user
// store.commit("app/setUser", user)持久化
但目前会有一个问题,每次刷新后数据都会丢失,为了解决这个问题,需要安装以下依赖
npm install vuex-persistedstate然后,在你的 Vuex store 中增加配置
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
plugins: [createPersistedState({
storage: window.sessionStorage, // 或者 localStorage
})]
});Pinia 使用
目前没有完全独立使用过 Pinia ,姑且记录下以便后期参考
该部分参考来自《Pinia 简单使用|教程|入门指引》
安装
npm install pinia全局引用 Pinia
// main.js
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia' //引入Pinia
import App from './App.vue'
const app = createApp(App)
app.use(createPinia()) // 挂载 Pinia 实例
app.mount('#app')在 src 同级新建 store 文件夹,并在内部创建 user.js
import { defineStore } from 'pinia'
// 通过 defineStore 定义一个名为 user 的Store
export const useUserStore = defineStore('user', {
// state 用于存储原始数据
state: () => {
return {
name: '张三',
age: 20,
}
},
// getters 计算属性:类似 Vue 组件的 computed,有缓存特性
getters: {
// 计算用户完整信息(接收 state 作为参数)
userInfo: (state) => `姓名:${state.name},年龄:${state.age * 2}`
},
// Actions 支持同步和异步方法
actions: {
increment() {
this.age++ // 同步,直接修改 state
},
async doubleCount() { // 异步
const res = await fetch("http://")
const data = await res.json()
this.age = data.data.age
}
}
});页面使用
import { useUserStore } from '@/stores/user'
const userStore = useUserStore() // 调用方法得到 Store 实例(注意:不要解构!会丢失响应式)
const changeName = () => {
userStore.age = 25 // 直接更新状态
userStore.doubleCount()
}如果使用解构的化,需如下调整
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia' // 导入 storeToRefs
const userStore = useUserStore()
// 解构并保持响应式(只解构 state / getters)
const { name, age } = storeToRefs(userStore)
// actions 直接解构即可(无需 storeToRefs)
const { increment } = userStore$patch()
如果需要一次性修改多个状态,可用 $patch 方法(性能更优)
// 方式1:对象形式(适合简单修改)
userStore.$patch({
name: '王五',
age: 30
})
// 方式2:函数形式(适合复杂逻辑,比如数组操作)
userStore.$patch((state) => {
state.age += 5
state.token = 'new-token-789'
})$subscribe 订阅
监听 Store 中 state 的所有变化(不管是直接修改、通过 $patch 修改,还是通过 actions 修改),只关心「状态最终变成了什么」,不关心「是谁触发的状态变化」
import { useUserStore } from '@/stores/user'
const useUserStore = useUserStore()
// 订阅 state 变化
const unsubscribe = useUserStore.$subscribe((mutation, state) => {
// 1. mutation:包含变化相关的信息
console.log('变化类型:', mutation.type) // 可选值:
// - direct:直接修改 state(如 userStore.age = 25)
// - patch object:通过 $patch 对象修改(如 $patch({age:25}))
// - patch function:通过 $patch 函数修改(如 $patch(state => state.age++))
console.log('Store 唯一标识:', mutation.storeId) // 如 'user'
console.log('变化前的快照(可选):', mutation.prevState) // 需要配置才会有
console.log('变化后的快照:', mutation.nextState)
// 2. state:变化后的最新 state
console.log('最新状态:', state)
// 常见场景:将状态持久化到 localStorage
localStorage.setItem('userState', JSON.stringify(state))
})
// 组件卸载时取消订阅(避免内存泄漏)
import { onUnmounted } from 'vue'
onUnmounted(() => {
unsubscribe() // 执行返回的函数即可取消订阅
})默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离:
useUserStore.$subscribe(
callback,
{
detached: true, // 即使组件卸载,订阅依然生效(默认 false)
flush: 'post', // 触发时机:pre(默认,DOM 更新前)/ post(DOM 更新后)/ sync(同步)
deep: true // 是否深度监听嵌套对象的变化(默认 true)
}
)$onAction() 订阅
$onAction() 是 监听 Store 中 actions 的调用过程,能捕获「action 执行前、执行后、执行出错」的全生命周期,关心「谁执行了什么操作」,而不是只关心状态变化
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 监听所有 actions 的执行
const unsubscribe = userStore.$onAction(({
name, // 当前执行的 action 名称(如 'updateName'、'login')
store, // 当前 Store 实例(等价于 userStore)
args, // 调用 action 时传入的参数数组(如调用 updateName('李四'),args 就是 ['李四'])
after, // 回调函数:action 成功执行后触发
onError // 回调函数:action 执行出错时触发
}) => {
// 1. 执行前的逻辑(比如记录日志、埋点)
console.log(`开始执行 action:${name},参数:`, args)
// 2. action 成功执行后触发
after((result) => {
console.log(`action ${name} 执行成功,返回值:`, result)
})
// 3. action 执行出错时触发(比如异步 login 接口报错)
onError((error) => {
console.error(`action ${name} 执行失败,错误:`, error)
})
})
// 取消监听(组件卸载时执行)
onUnmounted(() => {
unsubscribe()
})默认情况下,action 订阅器会被绑定到添加它们的组件上(如果 store 在组件的 setup() 内)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 true 作为第二个参数传递给 action 订阅器,以便将其从当前组件中分离:
useUserStore.$onAction(callback, true)两种订阅区别如下
| 维度 | $subscribe | $onAction() |
|---|---|---|
| 监听目标 | 监听 state 的最终变化 | 监听 actions 的执行过程 |
| 触发时机 | 状态发生变化后触发 | action 调用前、成功后、出错时触发 |
| 关注重点 | 「结果」:状态变成了什么 | 「过程」:执行了什么操作、参数 / 结果 / 错误 |
| 参数核心 | mutation(变化信息)、state(最新状态) | name(action 名)、args(参数)、after/onError(生命周期) |
| 典型场景 | 状态持久化(如 localStorage)、状态变更日志 | 行为埋点、操作权限校验、异步 action 错误处理 |
持久化
避免刷新浏览器的时候出现数据丢失,将需要持久化存储的数据添加到 localStorage 或 sessionStore 中,需要安装以下依赖
npm i pinia-plugin-persistedstate全局配置
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)在需要持久化的 store 中引入即可
因为我们定义 store 有两种方式,因此持久化也有对应的书写方式:
Store(state的方式)
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'll',
age: 18,
count: 100,
}),
//....
// 表示这个 store 里的数据都将持久化存储
persist: true
})Store(函数式方式)
import { defineStore } from 'pinia'
import { reactive, toRefs } from 'vue'
export const useUserStore = defineStore('user', () => {
return {
...toRefs(state),
fullName,
updateLastName
}
}, {
// 注意 defineStore 的第三个参数可以传入插件配置
persist: true
})只持久化部分数据
import { PersistedStateOptions } from 'pinia-plugin-persistedstate'
/**
* @description pinia 持久化参数配置
* @param {String} key 存储到持久化的 name
* @param {Array} paths 需要持久化的 state name
* @return persist
* */
const piniaPersistConfig = (key, paths) => {
const persist = {
key,
storage: window.localStorage,
// storage: window.sessionStorage,
paths
}
return persist
}
export default piniaPersistConfig修改 store 中的持久化配置
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 1 }),
// 开启数据缓存
persist: {
enabled: true
}
})
//或者
const useUserStore = defineStore('user', () => {
return {
...toRefs(state),
fullName,
updateLastName
}
}, {
// 注意defineStore的第三个参数可以传入插件配置,根据配置只持久化lastName
persist: piniaPersistConfig('user', ['lastName'])
})postMessage
postMessage 是 HTML5 引入的一个跨文档通信 API,允许不同窗口或 iframe 之间安全地发送消息。通过 postMessage 你可以向指定的窗口或 iframe 发送信息,并且接收方可以通过监听 message 事件来接收这些信息,如:
- 父页面 ↔ 子 iframe
- 页面 ↔ 新开弹窗(window.open)
- 同源多个标签页(也可用 BroadcastChannel)
- 页面 ↔ Service Worker(client.postMessage)
- 主线程 ↔ Web Worker(worker.postMessage)
发送端
targetWindow.postMessage(message, targetOrigin, [transfer]);- targetWindow: 目标窗口的引用,通常是通过 window.open()、iframe.contentWindow 或 window.parent 获取的;
- message: 要发送的消息,可以是任何 JavaScript 对象(通常是字符串、数字、对象等);
- targetOrigin: 目标窗口的源(协议 + 主机 + 端口),用于确保消息只发送给指定的源。可以使用 "*" 表示不限制源,但不推荐;
- transfer(可选): 一个可选的 Transferable 对象数组,用于传递所有权;
接收端
window.addEventListener('message', function(event) {
// event.data → 消息体
// event.origin → 发送方源(务必校验)
// event.source → 发送方 window 引用(可回发)
// event.ports → 携带的 MessagePort(如用 MessageChannel)
// 检查消息的来源是否可信
if (event.origin !== 'http://example.com') return;
// 处理接收到的消息
console.log('Received message:', event.data);
});