1. Pinia 状态管理

什么是 Pinia?

Pinia 是 Vue 3 官方推荐的状态管理库,可以理解为一个"全局数据仓库"。

为什么需要它? 想象一下,你有一个购物车数据,需要在多个页面(首页、商品页、结算页)共享。如果用 props 一层层传递会很麻烦,Pinia 就是解决这个问题的。

安装 Pinia

npm install pinia

使用步骤

第一步:在 main.js 中注册 Pinia
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')
第二步:创建 Store(数据仓库)
// src/stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// defineStore 第一个参数是 store 的唯一标识(名字)
export const useCounterStore = defineStore('counter', () => {
  // ========== state(数据)==========
  // 就像组件中的 ref,存放响应式数据
  const count = ref(0)
  const name = ref('小明')

  // ========== getters(计算属性)==========
  // 就像组件中的 computed
  const doubleCount = computed(() => count.value * 2)

  // ========== actions(方法)==========
  // 修改数据的方法
  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  // 异步操作也可以放在 actions 中
  async function asyncIncrement() {
    // 模拟接口请求
    await new Promise(resolve => setTimeout(resolve, 1000))
    count.value++
  }

  // 必须 return 出去才能在组件中使用
  return { 
    count, 
    name, 
    doubleCount, 
    increment, 
    decrement, 
    asyncIncrement 
  }
})
第三步:在组件中使用 Store

爷爷组件 (GrandParent.vue)

<template>
  <div class="grandparent">
    <h2>👴 爷爷组件</h2>
    <p>从 Pinia 获取的 count: {{ counterStore.count }}</p>
    <p>双倍 count: {{ counterStore.doubleCount }}</p>
    <button @click="counterStore.increment">爷爷点击 +1</button>
    
    <!-- 嵌套父组件 -->
    <ParentComponent />
  </div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import ParentComponent from './ParentComponent.vue'

// 获取 store 实例,直接使用即可
const counterStore = useCounterStore()
</script>

父组件 (ParentComponent.vue)

<template>
  <div class="parent">
    <h3>👨 父组件</h3>
    <p>从 Pinia 获取的 count: {{ counterStore.count }}</p>
    <button @click="counterStore.decrement">父亲点击 -1</button>
    
    <!-- 嵌套子组件 -->
    <ChildComponent />
  </div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import ChildComponent from './ChildComponent.vue'

// 任何组件都可以直接获取同一个 store
const counterStore = useCounterStore()
</script>

孙子组件 (ChildComponent.vue)

<template>
  <div class="child">
    <h4>👶 孙子组件</h4>
    <p>从 Pinia 获取的 count: {{ counterStore.count }}</p>
    <button @click="counterStore.asyncIncrement">孙子异步 +1(1秒后)</button>
  </div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'

// 三层组件都能访问同一份数据!
const counterStore = useCounterStore()
</script>

Pinia 小结

概念

说明

Store

全局数据仓库,所有组件都能访问

State

存放数据的地方(用 ref 或 reactive)

Getters

计算属性(用 computed)

Actions

修改数据的方法(普通函数,可以是异步的)


2. Provide / Inject 依赖注入

什么是 Provide / Inject?

这是 Vue 内置的"跨层级传值"方案,不需要安装额外的库。

  • provide(提供):祖先组件提供数据
  • inject(注入):后代组件接收数据

与 Pinia 的区别: Provide/Inject 的数据是"局部的",只在当前组件树内共享;而 Pinia 是"全局的"。

基本使用

爷爷组件(提供数据)
<template>
  <div class="grandparent">
    <h2>👴 爷爷组件 - Provide 示例</h2>
    <p>爷爷的消息: {{ message }}</p>
    <p>爷爷的主题色: {{ theme }}</p>
    <button @click="changeTheme">切换主题</button>
    
    <ParentProvide />
  </div>
</template>
<script setup>
import { ref, provide } from 'vue'
import ParentProvide from './ParentProvide.vue'

// ========== 定义要共享的数据 ==========
const message = ref('来自爷爷的问候~')
const theme = ref('light')

// 切换主题的方法
const changeTheme = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}

// ========== 使用 provide 提供数据 ==========
// provide(key, value) 
// key: 字符串或 Symbol,用于标识
// value: 要共享的数据

// 提供普通数据
provide('message', message)

// 提供响应式数据(子组件能感知变化)
provide('theme', theme)

// 提供方法(让子组件也能修改数据)
provide('changeTheme', changeTheme)

// 提供对象(包含多个相关数据)
provide('userInfo', {
  name: '张三',
  age: 60,
  role: 'grandparent'
})
</script>
父组件(中间层,不需要处理)
<template>
  <div class="parent">
    <h3>👨 父组件</h3>
    <p>我是中间层,不需要 inject,数据可以跳过我直接传给孙子</p>
    
    <ChildInject />
  </div>
</template>
<script setup>
import ChildInject from './ChildInject.vue'
// 注意:父组件不需要做任何事,数据会"穿透"过去
</script>
孙子组件(注入数据)
<template>
  <div class="child" :class="theme">
    <h4>👶 孙子组件 - Inject 示例</h4>
    
    <!-- 使用注入的数据 -->
    <p>收到爷爷的消息: {{ message }}</p>
    <p>当前主题: {{ theme }}</p>
    <p>用户信息: {{ userInfo.name }}, {{ userInfo.age }}岁</p>
    
    <!-- 调用爷爷提供的方法 -->
    <button @click="changeTheme">孙子也能切换主题</button>
  </div>
</template>
<script setup>
import { inject } from 'vue'

// ========== 使用 inject 接收数据 ==========
// inject(key) 或 inject(key, 默认值)

// 接收响应式数据
const message = inject('message')
const theme = inject('theme')

// 接收方法
const changeTheme = inject('changeTheme')

// 接收对象
const userInfo = inject('userInfo')

// 设置默认值(如果祖先没有 provide,就用默认值)
const notExist = inject('notExist', '这是默认值')
</script>
<style scoped>
.light {
  background: #fff;
  color: #333;
}
.dark {
  background: #333;
  color: #fff;
}
</style>

Provide / Inject 小结

特点

说明

适用场景

跨多层组件传值,避免 props 层层传递

响应式

如果 provide 的是 ref/reactive,子组件能响应变化

作用范围

只在当前组件树内有效

与 Pinia 对比

更轻量,但没有 Pinia 的状态管理能力

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐