在 Vue 3 项目中,如何使用 `Pinia` 替代 `Vuex`,并说明其在模块化、`TypeScript` 支持和 `SSR` 上的优势?

各位观众老爷们,大家好!今天咱们来聊聊一个前端界的大热门话题:如何在 Vue 3 项目里用 Pinia 替代 Vuex,以及 Pinia 凭啥能上位。这可不是单纯的喜新厌旧,而是技术发展的必然趋势。

开场白:Vuex,你辛苦了!

首先,我们要对 Vuex 致以崇高的敬意。毕竟,它曾经是 Vue.js 官方推荐的状态管理库,陪伴我们走过了无数个日夜。但是呢,随着 Vue 3 的到来,以及前端开发的日益复杂,Vuex 也暴露出了一些问题,比如:

  • 繁琐的 API: mutationsactionsgetters,是不是看着就头大?
  • 类型推断困难: 在 TypeScript 项目里,Vuex 的类型提示经常不给力,让人抓狂。
  • 模块化不够灵活: 模块之间的命名空间管理稍显笨重。

所以,我们需要寻找一个更现代、更高效、更易用的状态管理方案,而 Pinia,就是那个天选之子!

Pinia:新时代的弄潮儿

Pinia,一个由 Vue.js 核心团队成员开发的全新状态管理库,它汲取了 Vuex 的精华,并解决了 Vuex 的痛点。可以认为Pinia 是下一代的 Vuex。

Pinia 的优势:

特性 Pinia Vuex
API 简单直观,基于 Composition API,更易于理解和使用。 相对繁琐,需要定义 statemutationsactionsgetters
TypeScript 对 TypeScript 的支持非常友好,类型推断准确,编码体验更佳。 TypeScript 支持相对较弱,需要手动定义类型,容易出错。
模块化 模块化设计灵活,每个 store 都是独立的,易于组织和维护。 模块化需要使用 modules 选项,命名空间管理稍显笨重。
Devtools 与 Vue Devtools 集成良好,可以方便地查看和调试状态。 与 Vue Devtools 集成良好。
SSR 对 SSR 的支持更好,可以避免一些常见的 SSR 问题。 对 SSR 的支持相对复杂,需要额外配置。
体积 更小巧,性能更优异。 相对较大,性能稍逊。
Mutations 不再需要 mutations,可以直接在 store 中修改 state,更符合直觉。 需要通过 mutations 来修改 state,增加了代码的复杂性。

实践出真知:Pinia 的用法

光说不练假把式,接下来咱们就用代码来演示一下 Pinia 的用法。

1. 安装 Pinia

npm install pinia
# 或者
yarn add pinia
# 或者
pnpm add pinia

2. 创建 Pinia 实例

在你的 main.jsmain.ts 文件中,创建 Pinia 实例并将其挂载到 Vue 应用上:

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

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

app.use(pinia)
app.mount('#app')

3. 定义 Store

Pinia 的核心概念是 store,你可以理解为一个包含了特定状态和逻辑的模块。

// src/stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Counter Store',
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
    greeting: (state) => `Hello, ${state.name}!`,
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    incrementByAmount(amount: number) {
      this.count += amount
    },
    async fetchData() {
        //模拟异步请求
        return new Promise((resolve) => {
            setTimeout(() => {
                this.count = 100
                resolve(100)
            },1000)
        })
    }
  },
})

代码解读:

  • defineStore('counter', ...):定义一个名为 counter 的 store,第一个参数是 store 的唯一 ID,用于 Pinia 内部管理。
  • state: () => ({ ... }):定义 store 的状态,必须是一个函数,返回一个包含初始状态的对象。
  • getters: { ... }:定义 store 的计算属性,可以根据 state 派生出新的值。
  • actions: { ... }:定义 store 的方法,用于修改 state 或执行其他操作。

4. 使用 Store

在 Vue 组件中使用 useCounterStore

<template>
  <div>
    <p>{{ counter.count }}</p>
    <p>{{ counter.doubleCount }}</p>
    <p>{{ counter.greeting }}</p>
    <button @click="counter.increment()">Increment</button>
    <button @click="counter.decrement()">Decrement</button>
    <button @click="counter.incrementByAmount(5)">Increment by 5</button>
    <button @click="fetchData">Fetch Data</button>
  </div>
</template>

<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
import { onMounted } from 'vue'

const counter = useCounterStore()

const fetchData = async () => {
    await counter.fetchData()
}

onMounted(() => {
    console.log('Counter initial count:', counter.count)
})
</script>

代码解读:

  • import { useCounterStore } from '@/stores/counter':导入 useCounterStore 函数。
  • const counter = useCounterStore():调用 useCounterStore 函数,获取 store 实例。
  • counter.count:访问 store 的状态。
  • counter.doubleCount:访问 store 的计算属性。
  • counter.increment():调用 store 的方法。

模块化:让你的代码井井有条

Pinia 的模块化设计非常灵活,每个 store 都是独立的,可以根据业务需求进行拆分和组合。

例如,我们可以创建一个 user store 来管理用户信息:

// src/stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'John Doe',
    email: '[email protected]',
  }),
  actions: {
    updateName(name: string) {
      this.name = name
    },
  },
})

然后在组件中同时使用 counteruser store:

<template>
  <div>
    <p>Counter: {{ counter.count }}</p>
    <p>User: {{ user.name }}</p>
    <button @click="counter.increment()">Increment</button>
    <button @click="user.updateName('Jane Doe')">Update Name</button>
  </div>
</template>

<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'

const counter = useCounterStore()
const user = useUserStore()
</script>

TypeScript 支持:告别类型地狱

Pinia 对 TypeScript 的支持非常友好,类型推断准确,可以大大提升开发效率和代码质量。

在上面的例子中,我们可以看到,Pinia 能够自动推断出 stategettersactions 的类型,无需手动定义。

如果你想更精确地控制类型,可以使用 TypeScript 的泛型:

// src/stores/counter.ts
import { defineStore } from 'pinia'

interface CounterState {
  count: number
  name: string
}

export const useCounterStore = defineStore<'counter', CounterState>('counter', {
  state: () => ({
    count: 0,
    name: 'Counter Store',
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

SSR:让你的网站更快更强

SSR(Server-Side Rendering,服务端渲染)是一种提高网站性能和 SEO 的常用技术。Pinia 对 SSR 的支持非常好,可以避免一些常见的 SSR 问题。

在 SSR 环境下,我们需要确保每个请求都有一个独立的 Pinia 实例。

// server.js (Express 示例)
import express from 'express'
import { createSSRApp } from 'vue'
import { renderToString } from '@vue/server-renderer'
import { createPinia, setActivePinia } from 'pinia'
import App from './App.vue'

const app = express()

app.get('*', async (req, res) => {
  // 创建一个 Pinia 实例
  const pinia = createPinia()

  // 设置 Pinia 实例为激活状态
  setActivePinia(pinia)

  // 创建 Vue 应用
  const vueApp = createSSRApp(App)
  vueApp.use(pinia)

  // 渲染 Vue 应用
  const appHtml = await renderToString(vueApp)

  // 获取初始状态
  const piniaState = pinia.state.value

  // 将初始状态注入到 HTML 中
  const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>SSR with Pinia</title>
      </head>
      <body>
        <div id="app">${appHtml}</div>
        <script>window.__PINIA_STATE__ = ${JSON.stringify(piniaState)}</script>
        <script src="/client.js"></script>
      </body>
    </html>
  `

  res.send(html)
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

代码解读:

  • createPinia():在每个请求中创建一个新的 Pinia 实例。
  • setActivePinia(pinia):设置 Pinia 实例为激活状态,确保在服务端渲染过程中可以使用 Pinia。
  • pinia.state.value:获取 Pinia 的状态。
  • window.__PINIA_STATE__ = ${JSON.stringify(piniaState)}:将 Pinia 的状态注入到 HTML 中,以便客户端可以恢复状态。

在客户端,我们需要在应用启动时恢复 Pinia 的状态:

// client.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia, setActivePinia } from 'pinia'

const pinia = createPinia()

// 恢复 Pinia 的状态
if (window.__PINIA_STATE__) {
  pinia.state.value = JSON.parse(window.__PINIA_STATE__)
}

const app = createApp(App)
app.use(pinia)
app.mount('#app')

总结:Pinia,未来可期!

总而言之,Pinia 凭借其简洁的 API、强大的 TypeScript 支持和优秀的 SSR 体验,正在逐渐取代 Vuex,成为 Vue 3 项目的首选状态管理库。

当然,Pinia 也有一些不足之处,比如生态系统相对 Vuex 来说还不够完善。但是,随着 Pinia 的不断发展,相信它会越来越强大,为我们的前端开发带来更多的便利。

最后的建议:

如果你正在开发新的 Vue 3 项目,或者正在考虑迁移现有的 Vuex 项目,那么 Pinia 绝对值得你尝试。相信你会被它的简洁和高效所征服!

好了,今天的讲座就到这里。希望对大家有所帮助!如果有什么问题,欢迎在评论区留言。我们下期再见!

发表回复

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