State 状态 #

定义 State #

State 是 Store 的数据源,必须是一个返回初始状态的函数:

ts
export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'John',
    age: 25,
    email: 'john@example.com',
    preferences: {
      theme: 'dark',
      language: 'zh-CN'
    }
  })
})

为什么是函数? #

使用函数确保每个 Store 实例都有独立的状态副本:

ts
// 如果是对象,所有实例共享同一引用
state: {
  name: 'John'  // 错误!
}

// 使用函数,每个实例独立
state: () => ({
  name: 'John'  // 正确!
})

TypeScript 类型 #

ts
interface UserState {
  name: string
  age: number
  email: string
  preferences: {
    theme: 'dark' | 'light'
    language: string
  }
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    name: 'John',
    age: 25,
    email: 'john@example.com',
    preferences: {
      theme: 'dark',
      language: 'zh-CN'
    }
  })
})

访问 State #

直接访问 #

vue
<template>
  <div>
    <p>姓名: {{ userStore.name }}</p>
    <p>年龄: {{ userStore.age }}</p>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
</script>

解构访问 #

直接解构会失去响应性:

ts
const userStore = useUserStore()

// 错误:失去响应性
const { name, age } = userStore

使用 storeToRefs 保持响应性:

vue
<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// 正确:保持响应性
const { name, age } = storeToRefs(userStore)

// Actions 不需要 storeToRefs
const { updateName } = userStore
</script>

Setup Store 中的访问 #

ts
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  
  // 直接使用 .value
  function increment() {
    count.value++
  }
  
  return { count, increment }
})

修改 State #

直接修改 #

ts
const userStore = useUserStore()

// 直接修改单个属性
userStore.name = 'Jane'
userStore.age = 26

使用 $patch #

$patch 用于批量修改状态:

ts
// 对象形式
userStore.$patch({
  name: 'Jane',
  age: 26,
  email: 'jane@example.com'
})

// 函数形式(适合复杂逻辑)
userStore.$patch((state) => {
  state.name = 'Jane'
  state.age++
  state.preferences.theme = 'light'
})

通过 Actions 修改(推荐) #

ts
export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    age: 0,
    email: ''
  }),
  actions: {
    updateProfile(data: { name: string; age: number; email: string }) {
      this.name = data.name
      this.age = data.age
      this.email = data.email
    }
  }
})
vue
<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// 通过 action 修改
userStore.updateProfile({
  name: 'Jane',
  age: 26,
  email: 'jane@example.com'
})
</script>

重置 State #

使用 $reset() 将状态重置为初始值:

ts
const userStore = useUserStore()

// 修改状态
userStore.name = 'Jane'
userStore.age = 30

// 重置到初始状态
userStore.$reset()
// name: '', age: 0

Setup Store 的重置 #

Setup Store 需要手动实现 $reset

ts
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  
  function $reset() {
    count.value = 0
  }
  
  return { count, $reset }
})

替换 State #

使用 $state 完全替换状态:

ts
const userStore = useUserStore()

userStore.$state = {
  name: 'New User',
  age: 30,
  email: 'new@example.com',
  preferences: {
    theme: 'light',
    language: 'en-US'
  }
}

订阅 State 变化 #

$subscribe #

监听状态变化:

ts
const userStore = useUserStore()

userStore.$subscribe((mutation, state) => {
  // mutation.type: 'direct' | 'patch object' | 'patch function'
  // mutation.storeId: store 的 id
  // mutation.payload: $patch 的参数(仅 patch object 时)
  
  console.log('Type:', mutation.type)
  console.log('Store ID:', mutation.storeId)
  console.log('New State:', state)
  
  // 持久化到 localStorage
  localStorage.setItem('user', JSON.stringify(state))
})

订阅选项 #

ts
userStore.$subscribe((mutation, state) => {
  // ...
}, {
  // 是否在组件卸载后继续监听
  detached: true,
  
  // 立即执行一次
  immediate: true
})

全局订阅 #

监听所有 Store 的变化:

ts
// main.ts
const pinia = createPinia()

pinia.use(({ store }) => {
  store.$subscribe((mutation, state) => {
    console.log(`Store ${mutation.storeId} changed`)
  })
})

组合 State #

访问其他 Store #

ts
export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  getters: {
    // 在 getter 中访问其他 store
    userName(): string {
      const userStore = useUserStore()
      return userStore.name
    }
  },
  actions: {
    // 在 action 中访问其他 store
    checkout() {
      const userStore = useUserStore()
      console.log(`Checking out for ${userStore.name}`)
    }
  }
})

共享 State #

多个 Store 共享部分状态:

ts
// stores/shared.ts
import { ref } from 'vue'

export const sharedTheme = ref('dark')
ts
// stores/user.ts
import { defineStore } from 'pinia'
import { sharedTheme } from './shared'

export const useUserStore = defineStore('user', () => {
  const theme = sharedTheme
  
  function toggleTheme() {
    theme.value = theme.value === 'dark' ? 'light' : 'dark'
  }
  
  return { theme, toggleTheme }
})
ts
// stores/admin.ts
import { defineStore } from 'pinia'
import { sharedTheme } from './shared'

export const useAdminStore = defineStore('admin', () => {
  const theme = sharedTheme
  
  return { theme }
})

嵌套 State #

响应式更新嵌套对象 #

ts
export const useUserStore = defineStore('user', {
  state: () => ({
    profile: {
      name: 'John',
      address: {
        city: 'Beijing',
        country: 'China'
      }
    }
  })
})
ts
// 直接修改嵌套属性
userStore.profile.address.city = 'Shanghai'

// 使用 $patch
userStore.$patch((state) => {
  state.profile.address.city = 'Shanghai'
})

深拷贝 State #

使用 $state 获取深拷贝:

ts
const userStore = useUserStore()

// 获取深拷贝
const stateCopy = JSON.parse(JSON.stringify(userStore.$state))

// 或者使用 lodash
import { cloneDeep } from 'lodash'
const copy = cloneDeep(userStore.$state)

State 最佳实践 #

1. 初始化所有属性 #

ts
// 推荐
state: () => ({
  user: null,  // 明确初始化
  loading: false,
  error: null
})

// 不推荐
state: () => ({
  // user 未定义,可能导致错误
})

2. 使用 TypeScript #

ts
interface User {
  id: number
  name: string
  email: string
}

interface UserState {
  user: User | null
  loading: boolean
  error: string | null
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    user: null,
    loading: false,
    error: null
  })
})

3. 通过 Actions 修改 #

ts
// 推荐:通过 action 修改
userStore.updateName('Jane')

// 不推荐:直接修改(除非简单场景)
userStore.name = 'Jane'

4. 使用 $patch 批量修改 #

ts
// 推荐:批量修改使用 $patch
userStore.$patch({
  name: 'Jane',
  age: 26,
  email: 'jane@example.com'
})

// 不推荐:多次单独修改
userStore.name = 'Jane'
userStore.age = 26
userStore.email = 'jane@example.com'

下一步 #

现在你已经掌握了 State 的使用,接下来让我们学习 Getters。

最后更新:2026-03-28