4、State核心:定义State、访问State、重置State、批量修改State($patch)、State的响应式解构

State 是 Pinia 的心脏。说白了,它就是存放数据的地方。

我刚开始用 Vuex 的时候,总觉得 State 的定义有点绕。到了 Pinia,嗯,清爽多了。它就是一个函数,返回一个对象,对象里放着你所有的状态。

4.1 定义 State

在 Pinia 里定义 State,你只需要在 defineStore 里写一个 state 函数。这个函数返回一个对象,对象里的每个属性就是你的状态。

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '张三',
    age: 25,
    isLoggedIn: false,
    hobbies: ['coding', 'reading'],
    address: {
      city: '北京',
      district: '海淀'
    }
  })
})

我个人习惯把 State 写得尽量扁平。但有些场景下,嵌套对象确实更合理,比如用户信息里的地址。这时候要注意,Pinia 对嵌套对象的响应式处理是没问题的,但修改时最好用 $patch,后面会讲。

💡 小技巧: State 的类型会自动推导。如果你用 TypeScript,不需要额外声明类型,Pinia 会从 state 函数的返回值里自动推断。

4.2 访问 State

访问 State 有两种方式。一种是直接通过 store 实例访问,另一种是通过 storeToRefs 解构。

方式一:直接访问

const userStore = useUserStore()

console.log(userStore.name)  // '张三'
console.log(userStore.age)   // 25

这种方式最简单,也最常用。我在项目中大部分场景都用这种方式。但要注意,直接访问会丢失响应式吗?不会。因为 store 本身就是一个 reactive 对象。

方式二:通过 storeToRefs 解构

import { storeToRefs } from 'pinia'

const userStore = useUserStore()
const { name, age } = storeToRefs(userStore)

console.log(name.value)  // '张三'
console.log(age.value)   // 25

这里有个坑。如果你直接用解构赋值 const { name } = userStore,name 会丢失响应式。为什么?因为解构出来的只是一个普通的值,不是 ref。而 storeToRefs 会把每个属性都转成 ref,这样解构出来就是响应式的了。

⚠️ 注意: 千万不要用 const { name } = userStore 这种方式解构 State。我曾经在项目里踩过这个坑,数据变了页面不更新,排查了半天才发现是解构的问题。

4.3 重置 State

重置 State 是 Pinia 的一个亮点。你只需要调用 $reset() 方法,所有状态就会回到初始值。

const userStore = useUserStore()

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

// 重置
userStore.$reset()

console.log(userStore.name)  // '张三'
console.log(userStore.age)   // 25

这个功能在表单场景下特别实用。比如用户填写了一半想重新填,直接调用 $reset() 就行。我之前做后台管理系统时,每个表单页面都用了这个功能,省了不少代码。

但要注意,$reset() 只能重置通过 state 函数定义的初始值。如果你在 store 里动态添加了属性,它不会重置这些属性。

4.4 批量修改 State($patch)

当你需要同时修改多个状态时,一个个赋值太麻烦了。这时候 $patch 就派上用场了。

方式一:传入一个对象

userStore.$patch({
  name: '王五',
  age: 28,
  isLoggedIn: true
})

方式二:传入一个函数

userStore.$patch((state) => {
  state.name = '王五'
  state.age = 28
  state.isLoggedIn = true
  state.hobbies.push('swimming')
})

这两种方式有什么区别?

方式 适用场景 优点 缺点
对象方式 简单属性修改 代码简洁 不能处理复杂逻辑
函数方式 需要逻辑处理或操作数组 灵活,可以写任意逻辑 代码稍多

我个人更推荐函数方式。为什么?因为有时候你需要根据当前状态做判断,或者操作数组。比如上面的 hobbies.push('swimming'),用对象方式就做不到。

🎯 核心要点: $patch 的另一个好处是性能优化。它会批量触发一次更新,而不是每个属性修改都触发一次。如果你有多个状态要改,一定要用 $patch。

4.5 State 的响应式解构

前面提到了 storeToRefs,这里再深入讲一下。

响应式解构的核心就是 storeToRefs。它会把 store 里的每个属性都转成 ref,这样你解构出来就能保持响应式。

import { storeToRefs } from 'pinia'

const userStore = useUserStore()
const { name, age, hobbies } = storeToRefs(userStore)

// 在模板中使用
// <div>{{ name }}</div>
// <div>{{ age }}</div>

// 修改时要用 .value
name.value = '赵六'
age.value = 32

这里有个细节要注意:storeToRefs 只会解构 State 和 Getter,不会解构 Action。如果你解构了一个 Action,它不会报错,但会失去绑定,导致 this 指向错误。

⚠️ 避坑指南: 我曾经在项目里把 Action 也解构了,结果调用时发现 this 指向了 undefined。排查了半天才发现是解构的问题。记住,Action 不要解构,直接通过 store 调用。

还有一个场景:如果你只需要某个 State 的一部分,比如只需要 address.city,怎么办?

const { address } = storeToRefs(userStore)
// 这样 address 是一个 ref,address.value.city 是响应式的

// 或者直接解构嵌套属性
const city = computed(() => userStore.address.city)

我个人更推荐用 computed 来处理嵌套属性。因为 storeToRefs 解构出来的 address 是整个对象,如果你只想要 city,用 computed 更精确。

4.6 总结

State 是 Pinia 的基础,掌握好这几点就够了:

  • 定义:用 state 函数返回初始值
  • 访问:直接通过 store 实例,或者用 storeToRefs 解构
  • 重置:调用 $reset() 回到初始状态
  • 批量修改:用 $patch,推荐函数方式
  • 响应式解构:用 storeToRefs,别直接解构

下一章我们会讲 Getter,也就是 Pinia 里的计算属性。到时候你会发现,Getter 和 State 配合起来,能写出非常优雅的代码。