Vue 数据通信方法整理

父传子

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

函数可以接收父组件传递的属性和事件,在组件中可以直接使用这些属性和事件

useAttrsdefineProps 一起使用时,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

以下对比说明来自“豆包”

核心维度对比

特性VuexPiniaMitt
核心定位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>

mapGettersmapMutationsmutations

函数的用法同 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);
});
分类: 工作相关

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注