前端VuevueVue3 学习笔记
KaedeVue 3 核心技术
起步
为什么要学习 Vue 3
市场上, Vue 3 是一个必要的功能. 另外, Vue 3 已经成为了 Vue 的默认版本, Vue 2 已经成为了一个过去式, 不会有新的功能了. 不过 Vue 3 向前兼容, 大部分的内容都是可以直接使用的, 只需要一天的时间, 就可以上手 Vue 3.
Vue 3 有新的四个特性:
- 更容易进行维护. 组合式的 API, 并且更好的支持 TS.
- 更快的速度. 重写了 diff 算法, 模板编译优化, 同时更高效的组件初始化.
- 更小的体积. 按需引入即可.
- 更优化的数据响应式. Proxy 机制.
什么是组合式 API
之前我们写的叫做选项式 API, methods 都在 methods 中, data, watch, computed 都是一个一个的选项. 如果我们要实现一个功能, 代码就会被分散到各个地方, 不便于维护.
Vue 3 中, 就可以直接使用组合式 API, 将需要的东西写在一起, 直接调用方法就可以了. 功能 A 直接就是功能 A, 可以写在一起; 其他的功能也一样.
另外, Vue 3 中的组合式 API 写起来更加简单, 类似于 React 的函数式编程, 非常方便.
创建 Vue 3 项目
还是使用脚手架搭建一个新的项目: create-vue, 底层切换到了 Vite, 下一代的构建工具, 响应速度更快.
如果需要创建项目, 首先需要确认 NodeJS 版本: NodeJS >= 16. 随后, 直接在控制台执行命令即可创建:

这里暂时只需要勾选 ESLint 进行代码检测, 别的都可以不选择. 随后进入目录并且安装依赖, 即可直接启动项目!

可以看到, 这个项目启动只需要 988 ms, 一秒都不到! 这就是 Vite 的好处. 打开页面, 看到效果后, 项目就此创建完成!

熟悉项目目录 & 文件
打开刚才创建好的项目目录, 其实和之前的样子差不太多.

其中, vue.config.js 变成了 vite.config.js, 并且包管理器中的版本更高了, 直接查看一些关键文件吧, 主要发生变化的还是代码部分.
查看 main.js 中, 代码变成了这样子:
1 2 3 4 5 6
| import './assets/main.css'
import { createApp } from 'vue' import App from './App.vue'
createApp(App).mount('#app')
|
在 Vue 2 中, 我们是需要使用 new Vue() 来创建 Vue 实例的, 但是现在我们直接使用一个函数来实现功能. 这是对创建实例做了一个封装.
[!note] 封装
Vue 3 中, 很多东西都做了封装, 比如创建路由 createRouter, 创建仓库 createStore
这保证了多个实例的独立性
查看 App.vue 中, 还是三个部分. 并且除了样式, 后面的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup> import HelloWorld from './components/HelloWorld.vue' import TheWelcome from './components/TheWelcome.vue' </script>
<template> <header> <img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
<div class="wrapper"> <HelloWorld msg="You did it!" /> </div> </header>
<main> <TheWelcome /> </main> </template>
|
这里发现, script 上面多了一个 setup, 这代表可以直接编写组合式 API; 另外, template 中, 出现了多个根组件! 并且其中的组件也是不需要注册的, 直接使用就可以了, 和 react 很像.
在 index.html 中, 还是提供了一个挂载用的组件, 别的就没了.
1 2 3 4
| <body> <div id="app"></div> <script type="module" src="/src/main.js"></script> </body>
|
别的目录用法基本一样, components 还是存放组件.
组合式 API
介绍
组合式 API, 其实就是一系列的函数, 其中有各种的函数逻辑. 并且, 如果需要写组合式 API, 就必须要写 setup 作为标识.
setup 选项
执行时机
setup 其实也是一个选项. 在 Vue 2 的选项式中, 我们可以这样来写一个 setup, 它会在 beforeCreate 前面进行调用, 非常的早.
这里清空一下 App 中的代码, 删除其他的东西, 直接开始书写.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script> export default { setup() { console.log('setup 调用') }, beforeCreate() { console.log('before Create调用') } } </script>
<template> Hello </template>
|
查看控制台输出:

可以看到, 执行的比 beforeCreate 都要早! 也因为没有实例, 所以压根没有 this 的指向. (其实这是好事, 因为不需要纠结 this 是什么了!)
创建 & 使用数据
我们可以在 setup 中创建数据或者函数, 但是需要 return 才能使用. 如果没有 return, 则会报错:

所以, 必须要 return, 可以理解为暴露出去才可以正常使用.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <script> export default { setup() { // 数据 const message = 'Hello Vue3!' // 函数 const logMessage = () => { console.log(message) } // 暴露出去 return { message, logMessage } } } </script>
<template> <!--尝试直接使用setup中的数据--> <div>message is {{ message }}</div> <button @click="logMessage">调用函数</button> </template>
|
至此, 页面效果输出正常!

setup 语法糖
每次都需要 return 还是太复杂了, 所以为什么不简单一些呢? 其实在 Vue 3 中, 我们更加推荐直接在 script 标签后面加一个 setup, 一次性直接暴露其中的所有数据和函数.
1 2 3 4 5 6 7 8
| <script setup> // 数据 const message = 'Hello Vue3!' // 函数 const logMessage = () => { console.log(message) } </script>
|
现在, 页面上的数据和函数都可以正常访问, 简单多了!
其实 setup 语法糖的原理就是创建了一个 __sfc__ 对象, 这个对象中储存了所有的数据, 并且会自动 return 其中的内容, 进而简化写代码的量, 提升效率.
reactive 和 ref 函数
在 Vue 中, 默认的数据都不是响应式的. 只有通过 reactive 或者 ref 函数创建的数据才具有响应式的特征. 比如如下代码, 创建一个计数器, 以及对应的加减法按钮:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script setup> //定义Counter let counter = 0 // 定义修改变量的方法 const setCounter = (newValue) => { counter = newValue console.log(`new Counter is ${counter}`) } </script>
<template> <button @click="setCounter(counter - 1)">-</button> <span>{{ counter }}</span> <button @click="setCounter(counter + 1)">+</button> </template>
|
尝试查看一下效果:

可以发现, 数据发生了变化, 但是页面没有发生变化. 所以, 我们需要使用 reactive 或者 ref 函数来实现响应式. 对于 ref 来说, 直接传入值就好.
使用的时候:
- 页面中直接写 counter 即可.
- 代码中需要使用
counter.value 来获取, 或者操作其中的值.
- 需要导入 ref 或者 reactive 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup> // 引入ref import {ref} from 'vue'
//通过ref定义Counter let counter = ref(0) // 定义修改变量的方法 const setCounter = (newValue) => { counter.value = newValue console.log(`new Counter is ${counter.value}`) } </script>
<template> <button @click="setCounter(counter - 1)">-</button> <span>{{ counter }}</span> <button @click="setCounter(counter + 1)">+</button> </template>
|
至此, 已经实现响应式了.

对于 reactive 来说, 需要传入一个对象类型的数据, 而 ref 是通用的, 可以传入对象, 也可以传入简单类型. 使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup> // 引入ref import {reactive} from 'vue'
//通过ref定义Counter const counter = reactive({ count: 0 })
// 定义修改变量的方法 const setCounter = (newValue) => { counter.count = newValue } </script>
<template> <button @click="setCounter(counter.count - 1)">-</button> <span>{{ counter.count }}</span> <button @click="setCounter(counter.count + 1)">+</button> </template>
|
使用起来麻烦了一些, 需要套一个对象, 不过不需要 .value 来获取其中的值.
总结来说:
- reactive 和 ref 都可以生成响应式数据
- reactive 不能处理简单类型的数据, 必须是一个对象
- ref 支持更好, 但是必须通过
.value 访问修改
- ref 的内部基于 reative 实现
- 实际工作中, 推荐使用 ref
computed 函数
计算属性其实也应该和原来的属性在一起, 那么我们就可以使用 computed 函数来得到计算属性了. 计算属性的思想和 Vue 2 完全一致, 但是组合 API 下的写法变了.
- 导入 computed 函数
- 传入回调函数, 获取基于响应式的计算后的值
还是基于刚才的代码, 我希望获取两倍的数据, 就可以使用计算属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <script setup> import {computed, ref} from 'vue'
const counter = ref(0)
const setCounter = (newValue) => { counter.value = newValue }
// 创建一个计算属性 const doubleCounter = computed(() => { return counter.value * 2 })
</script>
<template> <button @click="setCounter(counter - 1)">-</button> <span>{{ counter }}</span> <button @click="setCounter(counter + 1)">+</button> <!--使用计算属性--> <strong>两倍为: {{ doubleCounter }}</strong> </template>
|
使用的时候还是一样的, 直接按照属性来写就好.

[!info] 可写的计算属性, get 和 set 呢?
和 Vue 2 完全一致, 直接将里面改写一下, 传入一个对象即可.
1 2 3 4 5 6 7 8 9 10 11
| const doubleCounter = computed({ get: () => counter.value * 2, set: (newValue) => { counter.value = newValue / 2 } })
const handleBlur = (event) => { doubleCounter.value = +event.target.value }
|
现在这个计算属性就是可写的了.

[!failure] 注意
- 计算属性中不应该出现副作用, 比如异步请求或者 DOM 操作
- 避免直接修改计算属性的值, 一般计算属性就是只读的
watch 函数
和 Vue 2 的作用完全一致, 可以侦听一个或者多个数据的变化, 和 computed 一样, 只是语法发生了变化.
多了两个额外的参数: immediate 和 deep, 代表立即执行和深度监视.
侦听单个数据
对于单个数据来说, 我们可以按照下面的这种来监听数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <script setup> import {ref, watch} from "vue";
const counter = ref(0);
// 监听数据的变化, 并且添加一个执行的回调函数 watch(counter, (newValue, oldValue) => { console.log(`old: ${oldValue}, new: ${newValue}`) console.log('counter变化了哦!') }) </script>
<template> <h1>{{ counter }}</h1> <button @click="counter--">-1</button> <button @click="counter++">+1</button> </template>
|
可以拿到新的值和老的值, 并且根据需要做需要做的事情. 这里面就适合做一些副作用的事情.
侦听多个数据
如果想要监听多个数据, 差不多, 不过第一个参数是一个数组, 第二个参数就是传入一个回调函数, 函数的每个参数都是一个数组, 代表新值和老值.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup> import {ref, watch} from "vue";
const counter = ref(0); const time = ref('1')
// 监听数据的变化, 并且添加一个执行的回调函数 watch([counter, time], ([newCounter, newTime], [oldCounter, oldTime]) => { console.log('time或者counter变了!') console.log(`${newCounter} -> ${oldCounter}`) console.log(`${newTime} -> ${oldTime}`) }) </script>
<template> <h1>{{ counter }}</h1> <button @click="counter--">-1</button> <button @click="counter++">+1</button> <button @click="time += '1'">Time + 1</button> </template>
|
[!tip] 注意
第一个数组中存储的都是新的值, 第二个数组中存储的都是旧的值!

额外配置项
对于函数来说, 我希望进入页面就执行, 或者需要深度监视, 都可以写在 watch 函数的第三个参数中, 作为一个对象进行传递即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <script setup> import {ref, watch} from "vue";
const counter = ref(0); watch(counter, (newValue) => { console.log(`new value is ${newValue}`) }, { // 进入页面立刻执行 immediate: true, // 深度监视 deep: true }) </script>
<template> <h1>{{ counter }}</h1> <button @click="counter--">-1</button> <button @click="counter++">+1</button> </template>
|
现在, 已进入页面就会触发回调, 并且是深度侦听的.

精确侦听某个属性
我现在有一个需求, 我希望一个对象的某个数据发生变化的时候, 我才调用侦听的函数, 应该怎么做呢? 这里需要用一个固定的语法: 第一个参数写: () => 对象.xxx.需要侦听的值
现在开始, 就实现了只侦听这一个数据了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <script setup> import {ref, watch} from "vue";
// 创建一个对象 const info = ref({ name: 'yyt', age: 18 })
// 深度侦听某个属性 watch(() => info.value.age, (newVal) => { // 这样就侦听成功了 console.log(`age变化了! 现在是 ${newVal}`) }) </script>
<template> <div>{{ JSON.stringify(info) }}</div> <button @click="info.name = 'lc'">改name</button> <button @click="info.age = 19">改age</button> </template>
|
上面的代码, 只有改变 Age 才会调用侦听器.

provide 和 inject 函数
这两个东西可以跨层级的, 向任意的底层组件传递数据和方法, 实现跨层级的组件通信. 相比 Vue 2, 这个东西使用起来更加方便一些.
比如我现在有 A->B->C 组件, 跨层级, 我的 A 想要拿到 C 的数据, 就可以使用跨层级的形式来传递了. 这个语法非常的简单:
1 2 3 4 5
| provide('key', 顶层组件中的数据)
const message = inject('key')
|
在顶层组件中, 可以准备数据, 并且进行 provide.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup> import ComA from "@/components/ComA.vue"; import {provide, ref} from "vue";
// 准备一个数据 const count = ref(0)
// 进行传递 provide('main-count', count) </script>
<template> <div>我是顶层组件, count is {{ count }}</div> <button @click="count--">-</button> <button @click="count++">+</button> <hr/> <ComA/> </template>
|
随后, 子组件中, 都可以访问到了.
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup> import ComB from "@/components/ComB.vue"; // 可以直接接收数据 import {inject} from "vue";
const count = inject('main-count') </script>
<template> <div>我是A组件, count is {{ count }}</div> <ComB/> </template>
|
同时, 数据是同步的!

那么, 我们尝试嵌套多层, 并且尝试修改一下:
1 2 3 4 5 6 7 8 9 10
| <script setup> import {inject} from "vue";
const count = inject('main-count') </script>
<template> <div>我是C组件, 在B的里面, count is {{ count }}</div> <button @click="count++">C尝试修改count + 1</button> </template>
|
查看效果如何:

可以进行修改! 这就是 provide 和 inject 的强大之处.
生命周期 函数
写法有一些差异, 不过大差不差其实, 所有的对应的钩子函数前面都多了一个 on , 并且 created 和 beforeCreated 被归类在了 setup 函数中. 比如进入页面就发送请求, 就可以放在 setup 中. 这里的意思就是直接写就好.
剩下的, 比如 mounted 周期函数, 就可以写在 onMounted 中进行调用. 只要调用对应的函数, 就是调用的生命周期函数. 调用语法如下:
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import {onBeforeMount, onMounted} from "vue";
onMounted(() => { console.log('Mounted生命周期') })
onBeforeMount(() => { console.log('beforeMounted调用') }) </script>
|
输出如下, 就算顺序不对, 效果也是正确的!

另外, 生命周期函数可以多次调用, 完全不会冲突, 而是按照顺序依次执行. 这样就可以把一个部分的东西写在一起, 方便维护代码.
组件间传参
父传子
基本的步骤如下:
- 父组件中给子组件绑定属性 (一样的)
- 子组件中, 通过 props 选项接收即可
这里的子组件, 我们通过另外一个函数: defineProps 来接收. 子组件中, 可以直接调用这个函数来定义传入的数据:
1 2 3 4 5 6 7 8 9 10 11
| <script setup> // 接收父组件的传入的参数 defineProps({ message: String }) </script>
<template> <!--直接使用就好--> <div>{{ message }}</div> </template>
|
父组件中, 直接导入组件并且传参即可:
1 2 3 4 5 6 7 8 9 10
| <script setup> // 引入组件 import ComSon from "@/components/ComSon.vue"; </script>
<template> <!--使用组件 并且传递参数--> <ComSon message="Hello World"/> <ComSon message="Good Lc And yyt"/> </template>
|

子传父
和 Vue 2 的思想是一样的:
- 父组件中给子组件通过
@ 绑定事件
- 子组件通过 emit 方法触发事件
这里的 emit 订阅还是通过一个编译器宏来实现.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <script setup> import {ref} from "vue";
// 子组件中, 声明一下可以订阅的方法 const emit = defineEmits(['get-counter'])
// 定义一个数据 const counter = ref(0)
// 可以调用这个函数 将子组件的数据传递给父亲 const getCounter = () => { emit('get-counter', counter.value) } </script>
<template> <div style="display: flex; justify-content: space-evenly"> <button @click="counter--">-</button> <div>{{ counter }}</div> <button @click="counter++">+</button> <button @click="getCounter">传递数据给父组件</button> </div> </template>
|
以上代码会在点击按钮后, 将数据传递给父组件.
父组件中, 可以绑定这个方法, 只要子组件发送了信号, 就可以进行数据的传递了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <script setup> // 引入组件 import ComSon from "@/components/ComSon.vue"; import {ref} from "vue";
// 定义变量 记录数据 const counter = ref(-1)
// 定义函数 用来从子组件获取数据 const setCounter = (newValue) => { counter.value = +newValue } </script>
<template> <!--使用组件 并且传递参数--> <h3 style="text-align: center"> 父组件: value = {{ counter }} </h3> <!--添加绑定数据--> <ComSon @get-counter="setCounter"/> </template>
|
这样就实现了一个手动的数据传递:

案例
既然都做到这里了, 我们尝试实现一个类似数据双向绑定. 不过这里我写的比较复杂一些. 子组件中, 我们只需要监听这个数据的变化, 就发送一个 input 信号即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <script setup> import {ref, watch} from "vue";
// 子组件中, 声明一下可以订阅的方法 const emit = defineEmits(['input'])
// 定义一个数据 const counter = ref(0)
// 数据变化的时候 就触发函数 watch(counter, (newValue) => { emit('input', +newValue) }) </script>
<template> <div style="display: flex; justify-content: space-evenly"> <button @click="counter--">-</button> <div>{{ counter }}</div> <button @click="counter++">+</button> </div> </template>
|
随后, 父组件中监听 @input 事件, 进行数据的获取, 并且修改 counter 的值即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script setup> // 引入组件 import ComSon from "@/components/ComSon.vue"; import {ref} from "vue";
// 定义变量 记录数据 const counter = ref(0) </script>
<template> <h3 style="text-align: center"> 父组件: value = {{ counter }} </h3> <!--尝试双向绑定--> <ComSon @input="args => {counter = args}"/> </template>
|
至此, 简单的绑定效果就实现了!

模板引用
获取 DOM
我们可以通过 ref 来获取页面中真实的 DOM 对象或者组件实例对象. 这样就可以快速的获取组件身上的属性和方法了. 比如登陆功能, 点击登陆的时候, 就可以直接获取输入框中的内容了.
这里还是使用 ref 对象, 不过一开始的 ref 需要传入一个 null 值. 随后, 给想要绑定的组件, 设置 ref 属性即可. 例如下面的登陆输入框案例.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup> import {ref} from "vue";
// 创建空的ref对象 const userNameRef = ref(null); const passwordRef = ref(null);
const handleClick = () => { /*这里渲染后 才可以访问到其中的数据*/ /*通过.value 访问即可*/ console.log(userNameRef.value) console.log(passwordRef.value) } </script>
<template> <div>用户名 <input ref="userNameRef"></div> <div>密码 <input ref="passwordRef"/></div> <button @click="handleClick">登陆</button> </template>
|
可以看到, 输出的东西就是 DOM 元素了, 那么自然, 我们就可以使用 .value 获取输入的值了.

获取组件
我们可以直接绑定一个子组件, 快速的获取其中存放的 value! 比如这样子, 写一个 counter 子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script setup> import {ref} from "vue";
const counter = ref(0) </script>
<template> <div> <button @click="counter--">-</button> Counter -> {{ counter }} <button @click="counter++">+</button> </div> </template>
|
接下来, 在父组件中, 尝试绑定这个组件并且输出数据看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script setup> import MyCounter from "@/components/MyCounter.vue"; import {ref} from "vue";
const counterRef = ref(null)
const getCounterInfo = () => { console.log(counterRef.value) } </script>
<template> <MyCounter ref="counterRef"/> <button @click="getCounterInfo">获取组件内容</button> </template>
|
输出发现, 对象上面没有我们想要的数据!

这是因为, setup 的语法糖中, 组件的属性默认是没有开放的. 如果需要访问, 需要使用一个宏函数.
所以这里回到子组件, 进行设置:
1 2 3 4 5 6 7 8 9 10
| <script setup> import {ref} from "vue";
const counter = ref(0)
// 暴露数据 defineExpose({ counter }) </script>
|
再次查看输出:

还没有展开, 就可以看到有一个 counter 了. 这个就是我们导出的内容! 不妨尝试输出看看:
1 2 3
| const getCounterInfo = () => { console.log(counterRef.value.counter) }
|

至此, 我们的属性已经成功获取了! 那么自然, 可以使用这个东西来实现数据的传递.
Vue 3.3 新特性
defineOptions
因为我们有的时候, 需要写很多的, 并列的嵌套的东西, 但是现在的组合式 API 是没法创建平级属性的, 有的时候就会很麻烦. 所以, 我们可以使用这个宏函数, 来实现添加和 setup 平级的属性.
1 2 3 4 5 6 7
| <script setup> // 我想要说明自己的名字是什么 可以配置额外选项 defineOptions({ name: 'ComCounter', inheritAttrs: false }) </script>
|
defineModel
我们之前如果想要实现双向绑定, 则需要写很多东西, 比如定义 props, 然后定义 emits, 这是非常繁琐没有必要的. 于是就有了这个新的特性, 可以快速的帮助我们实现数据的双向绑定!
父组件和子组件中, 都提供一个输入框用来演示. 子组件中, 可以这么写:
[!tip] 注意
这里的 :value 相当于绑定了一下数据, 另外 @input 实现了监听更改
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup> const inputText = defineModel({type: String}) </script>
<template> <div>子组件 <input :value="inputText" @input="e => inputText = e.target.value" > </div> </template>
|
随后, 父组件中, 只需要说明绑定的是什么, 然后传入需要绑定的值就行了.
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup> import ComSon from "@/components/ComSon.vue"; import {ref} from "vue";
/*定义一个输入的值 进行绑定*/ const inputText = ref('') </script>
<template> <div>App组件 <input v-model="inputText"></div> <ComSon v-model:model-value="inputText"/> </template>
|
现在就实现了双向数据绑定了!

Pinia
什么是 Pinia

Pinia 是 Vue 的最新状态管理工具, 是 Vuex 的替代品, 官方更加推荐我们使用 Pinia 进行状态管理. 这个状态管理的优势就是比 Vuex 更加简单, 并且只要有 Vuex 的基础, 上手 Pinia 就会非常快了.
Pinia 去掉了 modules 的概念, 每一个 store 都是一个独立的模块. 并且, 配合 TS 后, 使用起来回更加友好, 提供更加可靠的类型推断.
同时, Pinia 的 actions 可以直接操作 state, 逻辑更加清晰.
添加到项目
在一开始创建项目的时候就可以配置 Pinia, 也可以手动的添加到项目当中:
1 2 3
| yarn add pinia
npm install pinia
|
随后, 我们只需要创建一个 Pinia 实例, 并且挂载后就可以使用了. 来到 main.js 中, 书写如下代码:
1 2 3 4 5 6 7 8 9 10
| import {createApp} from 'vue' import {createPinia} from 'pinia' import App from './App.vue'
const pinia = createPinia()
const app = createApp(App) app.use(pinia) app.mount('#app')
|
这里拆分了一下 createApp, 不过其实是可以链式调用的.
基本语法
定义数据
这个是一个状态管理工具, 所以还是创建 store 文件夹. 另外考虑到一个文件就是一个仓库, 所以直接定义一个 counter.js 出来:

我们直接使用组合式 API 的写法就行, Vue 2 的和 Vuex 使用一摸一样, 传递一个对象就好. 在组合式 API 中, 基本使用代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import {defineStore} from "pinia"; import {ref} from "vue";
export const useCounterStore = defineStore('counter', () => { const count = ref(0) return { count } })
|
访问数据
随后, 在需要使用数据的地方, 导入这个仓库并且使用.
1 2 3 4 5 6 7 8 9
| <script setup> import ComSon from "@/components/ComSon.vue"; // 导入仓库 import {useCounterStore} from "@/store/counter.js"; // 得到仓库实例 const counterStore = useCounterStore() // 输出看看 console.log(counterStore) </script>
|
输出后, 可以找到刚才的数据:

那么我们就可以直接使用这两个数据了. (不要解构, 否则会失去响应式)
1
| <div>父组件 counter = {{counterStore.count}}</div>
|

成功地进行了访问! 在子组件中一摸一样, 直接使用即可.
1 2 3 4 5 6 7 8 9
| <script setup> import {useCounterStore} from "@/store/counter.js";
const counterStore = useCounterStore() </script>
<template> <div>子组件 counter == {{ counterStore.count }}</div> </template>
|
数据访问成功!

修改数据
当然, 直接提供操作方法就好, 一个函数就是一个 action, 修改 counter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import {defineStore} from "pinia"; import {ref} from "vue";
export const useCounterStore = defineStore('counter', () => { const count = ref(0)
const addCount = () => { count.value++ } const subCount = () => { count.value-- }
return { count, addCount, subCount } })
|
随后, 在需要使用的地方, 调用 store 身上的方法即可.
1 2 3 4 5 6
| <template> <div>父组件 counter = {{ counterStore.count }}</div> <button @click="counterStore.subCount">-</button> <button @click="counterStore.addCount">+</button> <ComSon/> </template>
|
至此, 数据已经可以正常的修改了, 并且所有使用的地方的数据都是响应式的!

计算属性
我们都知道, 之前有一个 getters, 可以获取经过一定计算的数据, 这里就没那么麻烦了, 直接使用 Vue 3 的计算属性即可.
1 2 3 4 5 6 7
| const doubleCount = computed(() => count.value * 2)
return { doubleCount }
|
随后, 就可以直接使用了, 一摸一样.
1
| <div>counter * 2 = {{ counterStore.doubleCount }}</div>
|

解构 store
storeToRefs
我们的 store 是不能随便的进行解构的. 一旦解构, 那么响应式就丢失了, 也就是修改后不会触发页面的修改. 如果希望解构后仍然保留响应式, 则可以使用这个方法来进行解构.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script setup> import ComSon from "@/components/ComSon.vue"; import {useCounterStore} from "@/store/counter.js"; import {storeToRefs} from "pinia";
const counterStore = useCounterStore()
// 如果需要进行解构 并且保留响应式 则需要使用方法 const {count, doubleCount} = storeToRefs(counterStore) </script>
<template> <div>父组件 counter = {{ count }}</div> <div>counter * 2 = {{ doubleCount }}</div> ... </template>
|
现在, 就可以直接使用解构后的数据, 并且保留响应式了.
方法解构
作为 actions, 其实是可以直接进行解构的, 不需要其他的方法来辅助. 至此, 完整的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup> import ComSon from "@/components/ComSon.vue"; import {useCounterStore} from "@/store/counter.js"; import {storeToRefs} from "pinia";
const counterStore = useCounterStore()
// 如果需要进行解构 并且保留响应式 则需要使用方法 const {count, doubleCount} = storeToRefs(counterStore) // 方法直接解构即可 const {addCount, subCount} = counterStore </script>
<template> <div>父组件 counter = {{ count }}</div> <div>counter * 2 = {{ doubleCount }}</div> <button @click="subCount">-</button> <button @click="addCount">+</button> <ComSon/> </template>
|
这样的代码就简单很多, 并且效果完善了!
数据持久化
基本配置
这里需要使用到一个插件: Pinia Plugin Persistedstate
首先, 安装一下这个包:
1
| npm i pinia-plugin-persistedstate
|
随后, 在 main.js 中注册这个东西:
1 2 3 4 5 6 7 8 9 10 11 12
| import {createApp} from 'vue' import {createPinia} from 'pinia'
import persist from 'pinia-plugin-persistedstate' import App from './App.vue'
const pinia = createPinia().use(persist)
const app = createApp(App) app.use(pinia) app.mount('#app')
|
使用
在需要持久化的模块中, 添加一个配置项即可. 比如上面的 Counter 模块, 可以在最后添加一个配置项:
1 2 3 4 5 6 7 8 9
| import {defineStore} from "pinia"; import {computed, ref} from "vue";
export const useCounterStore = defineStore('counter', () => { ...; }, { persist: true })
|
现在在页面中, 如果刷新页面, 就可以发现数据已经不会消失了!

一行代码都没改, 就是加了一个插件, 已经实现数据的持久化了!