Vue3总结

第一章:准备工作

第一节:Node安装

在进行 Vue 项目开发时,必须先安装Node.js。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。在 Vue 项目中,我们通常会使用到 Node.js 的 npm(Node Package Manager)来管理项目所需的各种库和工具,例如 Vite、Babel、ESLint 等。此外,Node.js 还可以用来运行开发环境中的本地服务器,以及编译、打包项目等。因此,安装 Node.js 是进行 Vue 项目开发的基础步骤。

一、nvm
1. nvm安装

nvm(Node Version Manager)是一个用来管理 node版本的工具。我们之所以需要使用 node,是因为我们需要使用 node中的 npm(Node Package Manager),使用 npm的目的是为了能够方便的管理一些前端开发的包!nvm的安装非常简单,步骤如下:

  1. 到这个链接下载 nvm的安装包:https://github.com/coreybutler/nvm-windows/releases
  2. 然后点击一顿下一步,安装即可!
  3. 安装完成后,还需要配置环境变量。在 我的电脑->属性->高级系统设置->环境变量->系统环境变量->Path下新建一个,把 nvm所处的路径填入进去即可!
  4. 打开 cmd,然后输入 nvm,如果没有提示没有找不到这个命令。说明已经安装成功!
  5. Mac或者 Linux安装 nvm请看这里:https://github.com/creationix/nvm。也要记得配置环境变.
2. nvm常用命令:
  1. nvm install node:安装最新版的 node.js。nvm i == nvm install。
  2. nvm install [version]:安装指定版本的 node.js
  3. nvm use [version]:使用某个版本的 node
  4. nvm list:列出当前安装了哪些版本的 node
  5. nvm uninstall [version]:卸载指定版本的 node
二、node:

安装完 nvm后,我们就可以通过 nvm来安装 node了。这里我们安装 21.7.1node.js就可以。安装命令如下:

nvm install 21.7.1
三、npm

npm(Node Package Manager)在安装 node的时候就会自动的安装了。当时前提条件是你需要设置当前的 node的版本:nvm use 21.7.1。然后就可以使用 npm了.
关于 npm常用命令以及用法,请看下文。

1. 安装包

安装包分为全局安装和本地安装。全局安装是安装在当前 node环境中,在可以在cmd中当作命令使用。而本地安装是安装在当前项目中,只有当前这个项目能使用,并且可以通过require引用。安装的方式只有 -g参数的区别:

npm install express      # 本地安装
npm install express -g   # 全局安装
2. 本地安装
  1. 将安装包放在 ./node_modules下(运行 npm 命令时所在的目录),如果没有 node_modules目录,会在当前执行 npm命令的目录下生成 node_modules目录。
  2. 可以通过 require()来引入本地安装的包。
3. 全局安装
  1. 将安装包放在 /usr/local下或者你 node的安装目录。
  2. 可以直接在命令行里使用。
4. 卸载包
npm uninstall [package]
5. 更新包
npm update [package]
6. 搜索包
npm search [package]
7.使用淘宝镜像:
npm install -g cnpm --registry=https://registry.npm.taobao.org

如果执行以上命令出现类似以下错误:

$ npm install -g cnpm --registry=https://registry.npm.taobao.org
npm ERR! code CERT_HAS_EXPIRED
npm ERR! errno CERT_HAS_EXPIRED
npm ERR! request to https://registry.npm.taobao.org/cnpm failed, reason: certificate has expired

npm ERR! A complete log of this run can be found in: C:\Users\paofu\AppData\Local\npm-cache\_logs\2024-03-27T06_46_40_597Z-debug-0.log

可以执行以下两条命令解决:

# 清理缓存
$ npm cache clean --force
# 取消ssl验证
$ npm config set strict-ssl false

以后就可以使用cnpm来安装包了,比如:

cnpm install express

第二节:VSCode

Vue的开发工具用的是 VSCode(Visual Studio Code),这款开发工具是微软官方出品,开源,免费,并且功能相当强大,使用者很多,插件相当丰富,是 Vue开发的不二之选。以下对 VSCode的使用做一个简单配置。

一、下载地址

https://code.visualstudio.com/

二、概念讲解

VSCode中分 WorkspaceFolderWorkspace相当于是一个项目的集合,可以添加许多的 Folder,在 Workspace中可以做好一些配置,那里面所有的 Folder都是按照这个配置来的,就不需要我们每次写个项目都配置一下了。而 Folder就是我们的每个项目的文件夹。

三、插件安装

后面开发 Vue项目,使用 .vue的单文件开发,就需要一些插件来帮我们识别 .vue文件。插件安装在 Extension中,点开即可看到一个搜索按钮,可以输入关键字搜索自己想要的插件。

这里我们开发 Vue推荐的几个插件:

  1. Vue - Official:Vue官方出品的识别 .vue语法的插件。
  2. Chinese (Simplified) :将编辑器设置为中文的插件。
  3. Javascript(ES6) code snippetsES6语法提示。
  4. Auto Rename Tag:自动重命名标签。标签都是成对出现的,开始标签修改了,结束标签也会跟着修改。
  5. Auto Close Tag:自动闭合标签。针对一些非标准的标签,这个插件还是很有用的。
  6. Prettier:格式化代码的插件。
  7. vscode-icons:可选。提供了很多类型的文件夹 icon,不同类型的文件夹使用不同的 icon,会让文件查找更直观。
  8. Vue VSCode Snippets:在编写vue代码时,可快速生成代码片段。
  9. Vue Peek:在Vue组件、模块中快速跳转。

更多插件大家可以自行搜索和安装。

第三节:VSCode快捷键

快捷键有五种组合方式(科普)
  1. Ctrl + Shift + ?:这种常规组合按钮
  2. Ctrl + C Ctrl +V:同时依赖一个按键的组合
  3. Shift + V C:先组合后单键的输入
  4. Ctrl + Click: 键盘 + 鼠标点击
  5. Ctrl + DragMouse: 键盘 + 鼠标拖动 macos下大多键位等同,Ctrl换成Command
通用快捷键
快捷键 作用
Ctrl+Shift+P,F1 展示全局命令面板
Ctrl+P 快速打开最近打开的文件
Ctrl+Shift+N 打开新的编辑器窗口
Ctrl+Shift+W 关闭编辑器
基础编辑
快捷键 作用
Ctrl + X 剪切
Ctrl + C 复制
Alt + up/down 移动行上下
Shift + Alt up/down 在当前行上下复制当前行
Ctrl + Shift + K 删除行
Ctrl + Enter 在当前行下插入新的一行
Ctrl + Shift + Enter 在当前行上插入新的一行
Ctrl + Shift +| 匹配花括号的闭合处,跳转
Ctrl + ] / [ 行缩进
Home 光标跳转到行头
End 光标跳转到行尾
Ctrl + Home 跳转到页头
Ctrl + End 跳转到页尾
Ctrl + up/down 行视图上下偏移
Alt + PgUp/PgDown 屏视图上下偏移
Ctrl + Shift + [ 折叠区域代码
Ctrl + Shift + ] 展开区域代码
Ctrl + K Ctrl + [ 折叠所有子区域代码
Ctrl + k Ctrl + ] 展开所有折叠的子区域代码
Ctrl + K Ctrl + 0 折叠所有区域代码
Ctrl + K Ctrl + J 展开所有折叠区域代码
Ctrl + K Ctrl + C 添加行注释
Ctrl + K Ctrl + U 删除行注释
Ctrl + / 添加关闭行注释
Shift + Alt +A 块区域注释
Alt + Z 添加关闭词汇包含
导航
快捷键 作用
Ctrl + T 列出所有符号
Ctrl + G 跳转行
Ctrl + P 跳转文件
Ctrl + Shift + O 跳转到符号处
Ctrl + Shift + M 打开问题展示面板
F8 跳转到下一个错误或者警告
Shift + F8 跳转到上一个错误或者警告
Ctrl + Shift + Tab 切换到最近打开的文件
Alt + left / right 向后、向前
Ctrl + M 进入用Tab来移动焦点
查询与替换
快捷键 作用
Ctrl + F 查询
Ctrl + H 替换
F3 / Shift + F3 查询下一个/上一个
Alt + Enter 选中所有出现在查询中的
Ctrl + D 匹配当前选中的词汇或者行,再次选中-可操作
Ctrl + K Ctrl + D 移动当前选择到下个匹配选择的位置(光标选定)
多行光标操作于选择
快捷键 作用
Alt + Click 插入光标-支持多个
Ctrl + Alt + up/down 上下插入光标-支持多个
Ctrl + U 撤销最后一次光标操作
Shift + Alt + I 插入光标到选中范围内所有行结束符
Ctrl + I 选中当前行
Ctrl + Shift + L 选择所有出现在当前选中的行-操作
Ctrl + F2 选择所有出现在当前选中的词汇-操作
Shift + Alt + right 从光标处扩展选中全行
Shift + Alt + left 收缩选择区域
Shift + Alt + (drag mouse) 鼠标拖动区域,同时在多个行结束符插入光标
Ctrl + Shift + Alt + (Arrow Key) 也是插入多行光标的[方向键控制]
Ctrl + Shift + Alt + PgUp/PgDown 也是插入多行光标的[整屏生效]
丰富的语言操作
快捷键 作用
Ctrl + Space 输入建议[智能提示]
Ctrl + Shift + Space 参数提示
Tab Emmet指令触发/缩进
Shift + Alt + F 格式化代码
Ctrl + K Ctrl + F 格式化选中部分的代码
F12 跳转到定义处
Alt + F12 代码片段显示定义
Ctrl + K F12 在其他窗口打开定义处
Ctrl + . 快速修复部分可以修复的语法错误
Shift + F12 显示所有引用
F2 重命名符号
Ctrl + K Ctrl + X 移除空白字符
Ctrl + K M 更改页面文档格式
编辑器管理
快捷键 作用
Ctrl + F4, Ctrl + W 关闭编辑器
Ctrl + k F 关闭当前打开的文件夹
Ctrl +
Ctrl + 1/2/3 切换焦点在不同的切割窗口
Ctrl + K Ctrl <-/-> 切换焦点在不同的切割窗口
Ctrl + Shift + PgUp/PgDown 切换标签页的位置
Ctrl + K <-/-> 切割窗口位置调换
文件管理
快捷键 作用
Ctrl + N 新建文件
Ctrl + O 打开文件
Ctrl + S 保存文件
Ctrl + Shift + S 另存为
Ctrl + K S 保存所有当前已经打开的文件
Ctrl + F4 关闭当前编辑窗口
Ctrl + K Ctrl + W 关闭所有编辑窗口
Ctrl + Shift + T 撤销最近关闭的一个文件编辑窗口
Ctrl + K Enter 保持开启
Ctrl + Shift + Tab 调出最近打开的文件列表,重复按会切换
Ctrl + Tab 与上面一致,顺序不一致
Ctrl + K P 复制当前打开文件的存放路径
Ctrl + K R 打开当前编辑文件存放位置【文件管理器】
Ctrl + K O 在新的编辑器中打开当前编辑的文件
显示
快捷键 作用
F11 切换全屏模式
Shift + Alt + 1 切换编辑布局【目前无效】
Ctrl + =/- 放大 / 缩小
Ctrl + B 侧边栏显示隐藏
Ctrl + Shift + E 资源视图和编辑视图的焦点切换
Ctrl + Shift + F 打开全局搜索
Ctrl + Shift + G 打开Git可视管理
Ctrl + Shift + D 打开DeBug面板
Ctrl + Shift + X 打开插件市场面板
Ctrl + Shift + H 在当前文件替换查询替换
Ctrl + Shift + J 开启详细查询
Ctrl + Shift + V 预览Markdown文件【编译后】
Ctrl + K v 在边栏打开渲染后的视图【新建】
集成终端
快捷键 作用
Ctrl + ` 打开集成终端
Ctrl + Shift + ` 创建一个新的终端
Ctrl + Shift + C 复制所选
Ctrl + Shift + V 复制到当前激活的终端
Shift + PgUp / PgDown 页面上下翻屏
Ctrl + Home / End 滚动到页面头部或尾部

原文链接:https://blog.csdn.net/crper/article/details/54099319

第二章:Vue基础

第一节:Vue介绍

一、Vue介绍

Vue(读音 /vjuː/,类似于 view) 是一套用于构建前后端分离的框架。刚开始是由国内优秀选手 尤雨溪开发出来的,目前是全球“最”流行的前端框架。使用 vue开发网页很简单,并且技术生态环境完善,社区活跃,是前后端找工作必备技能!Vue目前是3版本了。

  • Vue官网:https://cn.vuejs.org/
  • Vue Github:https://github.com/vuejs
二、Vue项目创建

创建Vue项目目前有两种方式,一种是使用 VueCli,另一种是使用 Vite,其中 VueCli已经处于维护模式了,过不了多久便不再支持,因此我们学习 Vite的方式创建。

使用 Vite 创建项目有以下优点:

  • 采用热重载功能,能快速的启动服务。
  • TypeScriptJSX等支持开箱即用。
  • 按需编译,不用等待整个应用编译完成。

使用 Vite 创建项目过程如下:

$ npm create vue@latest
或
$ npm create vue@3.10.3

接着会出现一些配置的提示:

# 输入项目名称
? 请输入项目名称: startvue

# 是否添加TypeScript支持
? 是否使用 TypeScript 语法?  否

# 是否添加JSX支持
? 是否启用 JSX 支持? 否

# 是否添加VueRouter
? 是否引入 Vue Router 进行单页面应用开发?  否

# 是否添加pinia
? 是否引入 Pinia 用于状态管理?  否

# 是否添加单元测试
? 是否引入 Vitest 用于单元测试?  否

# 是否添加端到端测试方案
? 是否要引入一款端到端(End to End)测试工具?  不需要

# 是否添加ESLint语法检查
? 是否引入 ESLint 用于代码质量检测?  是

# 是否添加Prettiert代码格式化
? 是否引入 Prettier 用于代码格式化? 否

# 是否引入Vue DevTools7调试
? 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) 是

创建完项目后,在项目根路径下输入命令:

$ cd startvue
$ npm install
$ npm run dev
三、项目结构介绍
node_modules:存放项目依赖的第三方包
public:一些公共的文件
src:项目的源代码,图片资源等
--assets:图片、字体等静态资源
--components:自定义组件
App.vue:项目的主入口vue文件
main.js:项目的主入口ts文件
index.html:项目的html文件
package.json:项目依赖包文件
vite.config.js:vite项目配置文件

第二节:组合式API

一、基本介绍

在Vue2中,使用的是 Options API (选项式API)。而Vue3中虽然也兼容 Options API ,但是更推荐 Composition API (组合)。那什么是 Options API,什么是 Composition API 呢?先来看以下例子:

<script>
export default {
  // data() 返回的属性将会成为响应式的状态
  // 并且暴露在 `this` 上
  data() {
    return {
      count: 0
    }
  },

  // methods 是一些用来更改状态与触发更新的函数
  // 它们可以在模板中作为事件处理器绑定
  methods: {
    increment() {
      this.count++
    }
  },

  // 生命周期钩子会在组件生命周期的各个不同阶段被调用
  // 例如这个函数就会在组件挂载完成后被调用
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

以上便是Options API ,如果改成Composition API ,那么要借助setup 语法。

二、setup介绍

setup 是Vue3 的配置项,它是一个函数,之前使用Options API 方式定义的数据、方法、计算属性、监听等数据,都是配置在setup 中。以下示例:

<script>
import { ref, onMounted } from 'vue'

export default {
  name: 'App',
  setup() {
    // 响应式状态
    // 响应式状态
    const count = ref(0)

    // 用来修改状态、触发更新的函数
    function increment() {
      count.value++
    }

    // 生命周期钩子
    onMounted(() => {
      console.log(`The initial count is ${count.value}.`)
    })

    return {
      count,
      increment
    }
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

在setup 中返回的对象,不管是数据还是方法,都可以直接在模板中使用。而为了实现方便,可以使用setup语法糖,即 ,这样就无须显示写setup 函数,写在script 中的所有数据和方法都可以直接在模板中使用。

<script setup>
import { ref, onMounted } from 'vue'

// 响应式状态
// 响应式状态
const count = ref(0)

// 用来修改状态、触发更新的函数
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
三、setup上指定组件名称

上述代码无法指定组件名称,如果想要指定组件名称,还需要额外添加一个script 标签:

<script>
export default {
  name: 'App',
}
</script>

为了解决这个问题,可以使用 vite-plugin-vue-setup-extend 插件来简化。步骤如下:

  • 安装:npm install vite-plugin-vue-setup-extend --save-dev
  • 配置:在 vite.config.js 中配置如下
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    VueSetupExtend()
  ],
})
  • script 标签上指定 name 属性:<script setup name="App">

Composition API 中新增了许多新的语法,我们后面再慢慢介绍。

关于 Options APIComposition API 谁更好的问题,这个仁者见仁智者见智,但是有一点需要注意,这个市场(招聘市场和开发市场)都在往 Composition API 上转(可见ElementPlus的文档),我们作为开发者,也必须要学会 Composition API 的使用。

第三节:响应式变量

使用组合式API的方式定义响应式变量,必须通过ref 或reactive 函数来实现。

一、使用ref函数定义响应式变量

语法为:let xxx = ref(初始值) 。使用如下:

<script setup name="App">
import { ref } from 'vue'

let username = ref('张三');

const onChangeUsername = () => {
  username.value = '李四';
}
</script>

<template>
  <div>用户名:{{ username }}</div>
  <button @click="onChangeUsername">修改用户名</button>
</template>

使用 ref函数定义的响应式变量,有两点需要注意:

  • 在模板中可直接使用变量。
  • script 标签中,若要操作变量,则需要通过 username.value 来实现。
二、使用reactive函数定义响应式变量

定义一些复合型响应式变量,则应该使用reactive 函数来实现。基本语法为:let xxx = reactive(源对象) 。使用如下:

<script setup name="App">
import { reactive } from 'vue'

let author = reactive({username: "张三", age: 18})

let books = reactive([
  {name: '水浒传',author: '施耐庵'},
  {name: '三国演义',author: '罗贯中'}
])


const onModifyBookName = () => {
  books[0].name = '红楼梦'
}

const onUpdateUsername = () => {
	author.username = "李四"
}

</script>

<template>
	<h1>用户名为:{{author.username}}</h1>
	<button @click="onUpdateUsername">修改用户名</button>
  <table>
    <thead>
      <tr>
        <th>书名</th>
        <th>作者</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="book in books" :key="book.name">
        <td>{{ book.name }}</td>
        <td>{{ book.author }}</td>
      </tr>
    </tbody>
  </table>
  <button @click="onModifyBookName">修改书名</button>
</template>

reactive 函数适合使用那些深层次的响应式变量。如果需要重新给reactive 函数定义的响应式变量赋值,那么需要通过Object.assign 来实现,否则将会失去响应式的功能:

Object.assign(books, [{name: '红楼梦',author: '曹雪芹'}])
三、ref和reactive对比
  • 如果要定义一个基本数据类型的响应式变量,则必须使用 ref
  • 如果要定义一个层级不深的复合类型的响应式变量,则 refreactive 都可以。
  • 如果要定义一个层级很深的复合类型的响应式变量,则推荐使用 reactive

第四节:模板语法1

一、文本:

html中通过 {{}}(双大括号)中可以把 Vue对象中的数据插入到网页中。并且只要 Vue对象上对应的值发生改变了,那么 html中双大括号中的值也会立马改变。

<script setup name="App">
import { ref } from 'vue'

let username = ref('张三')

function onModifyUsername() {
  username.value = '李四'
}
</script>

<template>
  <p>用户名:{{ username }}</p>
  <button @click="onModifyUsername">修改用户名</button>
</template>

当然,如果在html的{{}}中,第一次渲染完成后,不想要跟着后期数据的更改而更改,那么可以使用v-once指令来实现。示例代码如下:

<p v-once>{{username}}</p>
二、显示原生的HTML

有时候我们的Vue对象中,或者是后台返回给我们一个段原生的html代码,我们需要渲染出来,那么如果直接通过{{}}渲染,会把这个html代码当做字符串。这时候我们就可以通过v-html指令来实现。示例代码如下:

<script setup name="App">
let code = "<h1>欢迎来到知了课堂</h1>"
</script>

<template>
  <div v-html="code"></div>
</template>
三、属性绑定
<script setup name="App">
let classname = "main"
</script>

<template>
  <div class="classname">粉红色的回忆</div>
</template>

<style scoped>
.main{
  background-color: pink;
}
</style>

需要使用v-bind才能生效:

<template>
  <div v-bind:class="classname">粉红色的回忆</div>
</template>

或者可以把v-bind 省略,仅保留:class 即可:

<template>
  <div :class="classname">粉红色的回忆</div>
</template>
四、使用JavaScript表达式:

在使用了v-bind,或者{{}}的语法中。我们还可以执行一个JavaScript表达式。示例代码如下:

<script setup name="App">
let temp = 19;
</script>

<template>
  <div>{{ temp>18?'出去玩':'回家睡觉' }}</div>
</template>

注意,只能是JavaScript表达式,不能是语句,比如var a=1;a=2;这样的是js语句,不是表达式了。

五、条件判断:

在模板中,可以根据条件进行渲染。条件用到的是v-if、v-else-if以及v-else来组合实现的。示例代码如下:

<script setup name="App">
let weather = 'sun';
</script>

<template>
  <p v-if="weather == 'sun'">今天去公园玩!</p>
    <p v-else-if="weather == 'rain'">今天去看电影!</p>
    <p v-else>今天哪儿也不去!</p>
</template>
六、v-show和v-if:

v-if是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。 v-if也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 相比之下,v-show就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS进行切换。 一般来说,v-if有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show较好;如果在运行时条件很少改变,则使用v-if较好。

第五节:模板语法2

一、循环

在模板中可以用 v-for指令来循环数组,对象等。

1. 循环数组
<script setup name="App">
import { reactive } from "vue"

const books = reactive([
  {'title': '三国演义','author': '罗贯中'}, 
  {'title': '水浒传','author': '施耐庵'}, 
  {'title': '西游记','author': '吴承恩'}, 
  {'title': '红楼梦','author': '曹雪芹'}
])

</script>

<template>
  <table>
    <thead>
      <tr>
        <th>序号</th>
        <th>标题</th>
        <th>作者</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(book, index) in books">
        <td>{{ index }}</td>
        <td>{{ book.title }}</td>
        <td>{{ book.author }}</td>
      </tr>
    </tbody>
  </table>
</template>
2. 循环对象
<script setup name="App">
import { reactive } from "vue"

const person = reactive({
  "username": "知了",
  "age": 18,
  "homepage": "https://www.baidu.com/"
})

</script>

<template>
  <div v-for="(value, key) in person">
    {{ key }}:{{ value }}
  </div>
</template>
3. 保持状态

循环出来的HTML标签,如果没有使用key属性来唯一标识,如果后期的数据发生了更改,默认是会重用的HTML标签,并且标签的顺序不会跟着数据的顺序更改而更改。比如:

<script setup name="App">
import { reactive } from "vue"

const books = reactive(["红楼梦", "水浒传", "三国演义", "西游记"])

const changeBooks = () => {
  books.sort((x, y) => {
    return 5 - parseInt(Math.random() * 10)
  });
}

</script>

<template>
  <div v-for="(book, index) in books">
    <label for="book">{{ book }}</label>
    <input type="text" v-bind:placeholder="book">
  </div>
  <button v-on:click="changeBooks">更换图书</button>
</template>

如果你在某个input标签中输入了值,然后点击了“更换图书”的按钮,你会发现即使数据更改了,input并不会跟着数据的更改而更改,这时候我们只需要在v-for的时候加上一个key属性就可以了。示例代码如下:

<div v-for="(book,index) in books" v-bind:key="book">
    <label for="book">{{book}}</label>
    <input type="text" v-bind:placeholder="book">
</div>

注意,key只能是整形,或者是字符串类型,不能为数组或者对象。

4. 触发视图更新

Vue对一些方法进行了包装和变异,以后数组通过这些方法进行数组更新,会出发视图的更新。这些方法如下:

push():添加元素的方法

books.push("金瓶梅")

pop():删除数组最后一个元素。

books.pop()

shift():删除数组的第一个元素

books.shift()

unshift(item):在数组的开头位置添加一个元素。

books.unshift('金瓶梅')

splice(index,howmany,item1,...,itemX):向数组中添加或者删除或者替换元素。

// 向books第0个位置添加元素
books.splice(1,0,"金瓶梅")
// 下标从0开始,删除2个元素
books.splice(0,2)
// 下标从0开始,替换2个元素
books.splice(0,2,'金瓶梅','骆驼祥子')

sort(function):排序

books.sort(function(x,y){
	// 取两个随机数排序
	a = Math.random();
	b = Math.random();
  return a-b;
});

reverse():将数组元素进行反转。

books.reverse();
5. 覆盖数组

如果是使用ref 函数定义的数组,则可直接赋值,比如:

// ref类型的数组:books是一个数组的ref
let books = ref(["红楼梦", "水浒传", "三国演义", "西游记"])
books.value = ["红楼梦", "水浒传"]

如果是使用reactive 函数定义的数组,则通过以下方式覆盖数组:

// 通过定义成对象的方式
let books = reactive({
  value: ["红楼梦", "水浒传", "三国演义", "西游记"]
});
books.value = ["红楼梦", "水浒传"]

所以数组建议使用ref 函数来定义。

二、事件绑定
1. 基本使用

事件绑定就是在 HTML元素中,通过 v-on绑定事件的。事件代码可以直接放到 v-on后面,也可以写成一个函数。示例代码如下:

<script setup name="App">
import { reactive, ref } from "vue"

let count = ref(0);

const subtract = function (value) {
  count.value -= value;
}

</script>

<template>
  <p>{{ count }}</p>
  <button v-on:click="count += 1">加</button>
  <button v-on:click="subtract(10)">减10</button>
</template>

也可以简写成@click 来代替v-on:click 。

三、ref属性

ref属性用于在 script 标签中获取 template 中的元素。使用方法如下:

  • template 中的某个标签上定义 ref 属性。
  • script 中定义与 template 中与 ref 同名的变量。

示例代码如下:

<template>
    <input type="text" ref="usernameInput" placeholder="用户名"/>
    <button @click="showUsername">获取用户名</button>
</template>

<script setup>
import {ref} from 'vue'

let usernameInput = ref()
const showUsername = () => {
	console.log(usernameInput.value.value);
}
</script>
四、v-model

Vue中的响应式变量是双向的,如果想要通过表单元素(比如input、select)实时修改响应式变量,则可以通过v-model属性实现,示例代码如下:

<script setup>
import {ref} from "vue";
let username = ref("");

let category = ref(0);
</script>

<template>
<div>
<input v-model="username" />
<p>用户名为:{{username}}</p>
</div>

<div>
	<select v-model="category">
		<option value="1">Python</option>
		<option value="2">前端</option>
	</select>
	<p>分类为:{{category}}</p>
</div>
</template>

第六节:计算属性和监听属性

一、计算属性

一般情况下属性都是直接定义的,但是有些属性可能是需要经过一些逻辑计算后才能得出来,那么我们可以把这类属性变成计算属性。比如以下:

<script setup>
import {ref, computed} from "vue";

let width = ref(0);
let height = ref(0);

let area = computed(() => {
    return width.value*height.value;
})
</script>

<template>
    <label for="length">长:</label>
    <input type="number" name="height" v-model="height">
    <label for="width">宽:</label>
    <input type="number" name="width" v-model="width">
    <label for="area">面积:</label>
    <input type="number" name="area" :value="area" readonly>
</template>
二、监听属性

属性监听功能允许我们实时追踪属性值的变化。一旦属性值发生改变,我们可以通过相关函数立即获取到新的值。根据定义属性的方法的不同,属性监听主要分为两种情况:一种是针对使用ref函数定义的属性,另一种是针对使用reactive函数定义的属性。

1. 监听使用ref函数定义的属性

对于使用ref函数定义的基本数据类型,则直接监听即可,示例代码如下:

<script setup>
import {ref, watch} from "vue";

let width = ref(0);
watch(width, (newValue, oldValue) => {
	console.log(newValue);
})
const onUpdateWidth = () => {
	width += 1;
}
</script>

<template>
<div>
    <p>宽度:{{ width }}</p>
    <button @click="onUpdateWidth">修改width</button>
</div>
</template>

而对于使用ref函数定义的对象类型,如果要监听整个对象的变化,则直接监听即可:

<script setup>
import {ref, watch} from "vue";

let person = ref({
    username: '张三',
    age: 18
})

const onUpdatePerson = () => {
    person.value = {'username': '李四', "age": 20};
}

watch(person, (newValue, oldValue) => {
    console.log(newValue);
})
</script>

<template>
<div>
    <p>用户名:{{ person.username }},年龄:{{ person.age }}</p>
    <button @click="onUpdatePerson">修改person</button>
</div>
</template>

如果要监听属性下的子属性的变化,要么通过getter函数监听某个子属性,或者开启深度监听,监听所有子属性的变化:

<script setup>
import {ref, watch} from "vue";

let person = ref({
    username: '张三',
    age: 18
})

const onUpdateUsername = () => {
    person.value.username += "1"
}

const onUpdateAge = () => {
    person.value.age += 1
}

// 监听某个子属性的变化,使用getter方法
watch(() => person.value.username, (newValue, oldValue) => {
    console.log("new username: ", newValue);
})

// 监听整个对象子属性的变化,开启深度监听
watch(person, (newValue, oldValue) => {
    console.log("new person: ", newValue);
}, {deep: true})
</script>

<template>
<div>
    <p>用户名:{{ person.username }},年龄:{{ person.age }}</p>
    <button @click="onUpdateUsername">修改username</button>
    <button @click="onUpdateAge">修改age</button>
</div>
</template>
2.监听使用reactive函数定义的属性

对于使用reactive函数定义的属性,那么默认会开启深度监听,所以不管是监听一个子属性的变化,还是监听所有子属性的变化,都无需手动开启深度监听。示例代码如下:

<script setup>
import {ref, reactive, watch} from "vue";
let university = reactive({
    name: '清华大学', 
    year: 1911
})

const updateUniversityName = () => {
    university.name = '北京大学'
}

// 1. 监听一个子属性的变化
watch(() => univertisy.name, (newValue, oldValue) => {
	console.log('new name: ', newValue);
})

// 2. 监听所有子属性的变化
watch(university, (newValue, oldValue) => {
    console.log(newValue);
})
</script>

<template>
<div>
    <div>大学名称:{{ university.name }}</div>
    <button @click="updateUniversityName">更换大学名称</button>
</div>
</template>

第七节:生命周期函数

生命周期函数代表的是 Vue实例,或者是 Vue组件,在网页中各个生命阶段所执行的函数。生命周期函数可以分为创建阶段、挂载阶段、更新阶段以及卸载阶段。

  • 创建阶段:setup
  • 挂载阶段:onBeforeMountonMounted
  • 更新阶段:onBeforeUpdateonUpdated
  • 卸载阶段:onBeforeUnmountonUnmounted
<template>
    <h2>count为:{{ count }}</h2>
    <button @click="onUpdateCount">更新count</button>
</template>

<script setup>
import {
    ref,
    onBeforeMount,
    onMounted,
    onBeforeUpdate,
    onUpdated,
    onBeforeUnmount,
    onUnmounted
} from 'vue'

// 数据
let count = ref(0)
// 方法
function onUpdateCount() {
    count.value += 1
}
console.log('setup')
// 生命周期钩子
onBeforeMount(() => {
    console.log('挂载之前')
})
onMounted(() => {
    console.log('挂载完毕')
})
onBeforeUpdate(() => {
    console.log('更新之前')
})
onUpdated(() => {
    console.log('更新完毕')
})
onBeforeUnmount(() => {
    console.log('卸载之前')
})
onUnmounted(() => {
    console.log('卸载完毕')
})
</script>

第八节:自定义组件

一、定义属性

有时候我们定义一个组件后,需要让调用者传入参数,那么就可以将参数定义成属性。示例代码如下:

  1. Person组件代码:
<script setup name="Person">
// Person组件
import {defineProps} from "vue";

// 1. 使用数组的形式
//const props = defineProps(['username'])
// 2. 使用对象的形式
const props = defineProps({
    gender: {
        type: String,
        default: '男'
    },
    username: String,
    age: Number,
    body: {
        height: Number,
        weight: Number
    }
})

</script>

<template>
<p>
    用户名:{{ props.username }},
    年龄:{{ props.age }},
    身高:{{ props.body.height }},
    体重:{{ props.body.weight }}
</p>
</template>

子组件不能修改父组件传过来的 props ,否则会引起错误。

  1. App.vue组件代码:
<script setup>
import Person from "./components/Person.vue"
</script>

<template>
<Person :age="18" username="张三" :body="{height: 180, weight: 140}"></Person>
</template>
二、自定义事件

子组件可以自定义事件,然后父组件可以实现发生这个事件的处理函数。示例代码如下:

  1. Person.vue组件:
<script setup name="Person">
import {defineEmits, ref} from "vue";

let steps = ref(0);

const emit = defineEmits(['change']);

const onUpdate = () => {
    steps.value += 10
    emit('change', steps.value);
}

</script>

<template>
<button @click="onUpdate">行走10步</button>
</template>
  1. App.vue组件:
<script setup>
import Person from "./components/Person.vue"

const onPersonChange = (steps) => {
    console.log("步行了:", steps);
}
</script>

<template>
<Person @change="onPersonChange"></Person>
</template>
三、定义v-model

使用 v-model 可以定义一个在父组件和子组件中双向绑定的对象。示例代码如下:

  1. Person.vue组件代码:
<script setup name="Person">
import {defineModel, watch} from "vue";

let steps = defineModel();
const onUpdate = () => {
    steps.value += 10;
}
watch(steps, (newValue, oldValue) => {
    console.log("Person中监听到steps:", newValue);
})

</script>

<template>
<button @click="onUpdate">行走10步</button>
</template>
  1. App.vue组件代码:
<script setup>
import {ref, watch} from "vue";
import Person from "./components/Person.vue"

let steps = ref(0);
watch(steps, (newValue, oldValue) => {
    console.log("App中监听到的:", newValue);
})
</script>

<template>
<Person v-model="steps"></Person>
</template>
四、插槽

插槽(Slots)在Vue中是一种非常重要的内容组织方式,它能够让你在子组件中留下预先定义的位置,然后在父组件中填充这些位置。

1. 基本使用:

示例代码如下:

  1. SubmitButton.vue代码:
<template>
<button type="submit">
    <slot></slot>
</button>
</template>
  1. App.vue代码:
<template>
<SubmitButton>登录</SubmitButton>
</template>

“登录”这个文本就会填充在SubmitButton的slot标签处。或者也可以在定义插槽的时候指定默认值,比如:

// SubmitButton.vue代码
<template>
<button type="submit">
    <slot>提交</slot>
</button>
</template>

那么以后在使用SubmitButton组件的时候,使用 也会有提交两个文字。

2. 具名插槽:

如果在一个组件中想要定义多个插槽,则可以使用“具名”插槽,就是给每个插槽指定一个名称,父组件使用的时候,也要指明相应代码要放到哪个插槽下,比如我们定义一个 BaseLayout 组件:

<div class="container">
  <header>
    <!-- 标题内容放这里 -->
  </header>
  <main>
    <!-- 主要内容放这里 -->
  </main>
  <footer>
    <!-- 底部内容放这里 -->
  </footer>
</div>

其中header 、main 、footer 是由父组件自由指定的,那么组件的代码可以修改为:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

其中有三个插槽,分别是header、默认的插槽、以及footer插槽。那么在父组件中可以按照如下方式使用:

<BaseLayout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <template #default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>

对于default插槽,可以不用指明名称,因此也可以如下使用:

<BaseLayout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <!-- 隐式的默认插槽 -->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>
3. 插槽作用域:

在父组件中,如果使用插槽,则插槽中的代码是无法访问子组件中的变量的。如果想要访问,那么必须在定义插槽的时候显示指定。示例代码如下:

  1. BaseLayout.vue组件代码:
<div class="container">
  <header>
    <slot name="header" :tab="1"></slot>
  </header>
  <main>
    <slot :card="2"></slot>
  </main>
  <footer>
    <slot name="footer" :box="3"></slot>
  </footer>
</div>

App.vue组件代码:以后在父组件中,可以通过以下方式,获取到子组件中传递过来的插槽信息:

<BaseLayout>
  <template #header="headerProps">
    {{ headerProps }}
  </template>

  <template #default="defaultProps">
    {{ defaultProps }}
  </template>

  <template #footer="footerProps">
    {{ footerProps }}
  </template>
</BaseLayout>

第三章:VueRouter路由

第一节:VueRouter基本

在网页中,经常需要发生页面更新或者跳转。这时候我们就可以使用 Vue-Router来帮我们实现。Vue-Router是用来做路由的,也就是定义 url规则与具体的 View映射的关系。可以在一个单页面中实现数据的更新。

一、安装:

通过命令:npm install vue-router@4.3.0 --save ,或者在创建项目的时候,选择添加Vue-Router的支持。

二、基本使用:
  1. 创建路由代码:Single Page Application:SPA

在src/router/index.js中,填入以下代码:

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router

以上代码解释如下:

  1. 使用 createRouter 创建路由对象。

  2. 路由工作模式,有两种工作模式:

    • history模式:路由中不包含#,更加美观,但是在后期部署后,如果前端项目代码和后端项目代码部署在同一个域名下,则容易产生路由混淆(前端路由和后端路由混淆在一起了)。以上代码用的是history模式,代码为:

      const router = createRouter({
       history:createWebHistory(), //history模式
      })
      
    • hash模式:路由中包含#,不太美观,对SEO优化不够友好。好处是兼容性更好,即使前端代码和后端代码部署在同一个域名下,也不会产生路由混淆。使用hash模式的代码为:

       const router = createRouter({
      history:createWebHashHistory(), //hash模式
      })
      
  3. routes是一个数组,里面是对象数据,每个对象中的属性作用分别如下:

  • path:该路由对应的path。
  • component:组件对象,既可以先导入,也可以指定组件的时候使用import语句来导入。
  • name:路由名称,后期在做路由反转的时候可以使用。
2. 使用路由

然后在main.js文件中对router进行使用:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)

app.mount('#app')

以上相关代码解释如下:

  1. 从src/router/index.js中导入router对象。
  2. 执行app.use(router)代码,即可使用此路由了。
3. 展示路由:

在App.vue中的代码如下:

<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>

<template>
  <header>
    <div class="wrapper">
      <nav>
        <RouterLink to="/">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView />
</template>

以上相关代码解释如下:

  1. 在模板中,使用RouterLink定义路由链接,此组件有点类似html中的a标签,在点击后会跳转到to属性指定的路由上去。to除了直接写path外,还可以通过name来定义,比如:

    <RouterLink :to="{name: 'home'}">Home</RouterLink>
    
  2. 使用RouterView标识路由出口,在进入路由A后,路由A所对应的组件会显示在RouterView的地方。

二、嵌套路由

在某个页面中,如果还需要通过路由更新局部模块,那么可以通过嵌套路由来实现,只要配置children就可以了。

  1. src/router/index.js代码如下:
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },{
      path: '/news',
      component: () => import('../views/news/NewsView.vue'),
      name: 'news',
      children: [{
        path: 'detail', 
        name: 'news-detail', 
        component: import('../views/news/NewsDetailView.vue')
      }]
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('../views/AboutView.vue')
    }
  ]
})
  1. src/views/news/NewsView.vue代码如下:
<template>
<ul>
    <li><RouterLink :to="{name: 'news-detail'}">新闻1</RouterLink></li>
    <li>新闻2</li>
    <li>新闻3</li>
</ul>
<RouterView></RouterView>
</template>
  1. src/views/news/NewsDetailView.vue代码如下:
<template>
<h1>AIGC将迎来爆发式增长!</h1>
</template>
三、路由传参

路由传参有两种方式,一种是在定义路由的时候指定参数,另一种是通过查询字符串的方式传参。

1. 路由指定参数

src/router/index.js代码如下:

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
      path: '/news',
      component: () => import('../views/news/NewsView.vue'),
      name: 'news',
      children: [{
        path: 'detail/:pk',
        name: 'news-detail',
        component: import('../views/news/NewsDetailView.vue')
      }]
    }
  ]
})

在使用这个路由的时候,其代码如下:

<RouterLink :to="{name: 'news-detail', params: {pk: 1}}">新闻1</RouterLink>

以后在NewsDetailView.vue的onMounted生命周期函数中可以通过以下方式获取到参数:

<script setup name="NewsDetailView">
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';

const route = useRoute();

onMounted(() => {
    const pk = route.params.pk;
    console.log(pk);
})
</script>
2. 查询字符串传参

使用查询字符串传参,不需要预先在路由定义的时候指定参数,使用的时候直接传递即可,示例代码如下:/news/detail?pk=1&page=2

<RouterLink :to="{name: 'news', query: {page: 1}}">News</RouterLink>

以后在NewsView.vue组件中的onMounted生命周期函数中通过以下代码即可获取到:

<script setup name="NewsView">
import { onMounted } from 'vue';
import {useRoute} from "vue-router";

const route = useRoute();

onMounted(() => {
    console.log(route.query);
})
</script>
四、编程式导航

之前我们学习了使用 <router-link>可以在用户点击的情况下进行页面更新。但有时候我们想要在 js中手动的修改页面的跳转,这时候就需要使用编程式导航了。

1. router.push跳转:

想要导航到不同的 URL,则使用 router.push方法。这个方法会向 history栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL

当你点击 <router-link>时,这个方法会在内部调用,所以说,点击 <router-link :to="...">等同于调用 router.push(...)

声明式 编程式
<router-link :to="..."> router.push(...)

示例代码如下:

<script setup>
import {useRouter} from "vue-router";

const router = useRouter();

// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })

// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
</script>

注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path :

const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user
2. router.replace替换

它的作用类似于 router.push,唯一不同的是,它在导航时不会向 history 添加新记录,正如它的名字所暗示的那样——它取代了当前的条目。

声明式 编程式
<router-link :to="..." replace> router.replace(...)

也可以直接在传递给 router.pushto 参数中增加一个属性 replace: true

router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })

第二节:VueRouter进阶

一、导航守卫

导航守卫分为:全局守卫、组件导航守卫以及路由导航守卫。下面分别来进行讲解。

1. 全局守卫:

全局守卫有 beforeEachafterEach ,即在跳转前和跳转后的导航守卫。

首先看下 beforeEach,示例代码如下:

router.beforeEach((to, from) => {
  if (
    // 检查用户是否已登录
    !isAuthenticated &&
    // ❗️ 避免无限重定向
    to.name !== 'Login'
  ) {
    // 将用户重定向到登录页面
    return { name: 'Login' }
  }
})

如果beforeEach的回调函数中返回的是true或者什么都没返回,则跳转到默认的路由,否则跳转到返回的路由。

afterEach导航路由为已经改变完成后的守卫。示例代码如下:

router.afterEach(function(to,from){
  console.log('to:',to);
  console.log('from:',from);
})
3. 组件内导航守卫:

组件内导航守卫只能使用以下两个方法:

  • onBeforeRouteLeave :在离开当前路由前调用。如果返回false,将会取消路由离开。比如:

    beforeRouteLeave (to, from) {
    const answer = window.confirm('本页面还未保存,您确定要离开吗?')
    if (!answer) return false
    }
    

}

  • onBeforeRouteUpdate :在路由发生更新时调用,比如从/user/1 更新到/user/2 ,就会执行该守卫方法。一般这个方法用的比较少。
  1. 路由导航守卫:

在定义路由的时候,可以指定beforeEnter导航守卫,示例代码如下:

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]
二、路由元信息

有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的 meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。定义路由的时候你可以这样配置 meta 字段:

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    children: [
      {
        path: 'new',
        component: PostsNew,
        // 只有经过身份验证的用户才能创建帖子
        meta: { requiresAuth: true },
      },
      {
        path: ':id',
        component: PostsDetail
        // 任何人都可以阅读文章
        meta: { requiresAuth: false },
      }
    ]
  }
]

那么以后可以通过路由.meta 访问到元数据。示例代码如下:

router.beforeEach((to, from) => {
  // 而不是去检查每条路由记录
  // to.matched.some(record => record.meta.requiresAuth)
  if (to.meta.requiresAuth && !auth.isLoggedIn()) {
    // 此路由需要授权,请检查是否已登录
    // 如果没有,则重定向到登录页面
    return {
      path: '/login',
      // 保存我们所在的位置,以便以后再来
      query: { redirect: to.fullPath },
    }
  }
})

第四章:Pinia

Pinia是Vue.js应用程序的状态管理库。它提供了一种简单,轻量级的解决方案,用于在Vue应用程序中管理和维护状态。Pinia库的特点是易于使用和集成,可以使开发者在不牺牲性能的情况下更有效地处理和维护状态。pinia中有三个概念,分别是:state、getter、action,对应于Vue组件中的:data、computed、methods。

一、安装和配置:

  1. 安装:通过命令:npm install pinia@2.1.7 ,或者在创建vue项目的时候勾选使用Pinia。
  2. 配置:在main.js中,需要创建pinia对象,并与app对象进行绑定,示例代码如下:
import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'

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

二、基本使用

通常在src目录下创建一个stores文件夹,然后在里面按需创建js文件。假设要创建一个用于管理counter全局变量的库文件,那么可以创建counter.js文件,然后填入以下代码:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }

  return { count, increment }
})

或者使用选项式API:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // 也可以这样定义
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++
    },
  },
})

这样就定义好了一个count变量,以后在组件中可以通过以下三种方式修改:

<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
// 1. 直接修改
counterStore.count++
// 2. 使用$patch批量修改
counterStore.$patch({ count: counterStore.count + 1 })
// 3. 使用action修改
counterStore.increment()
</script>
<template>
  <!-- 直接从 store 中访问 state -->
  <div>Current Count: {{ counter.count }}</div>
</template>

以上三种修改方式的应用场景如下:

  1. 如果只要修改一个状态变量,并且不需要额外的操作,那么推荐使用第一种方法。
  2. 如果要一次性修改多个状态变量,那么推荐使用$patch方法,效率更高。
  3. 如果在修改状态变量的同时要做一些额外的操作,那么推荐第三种方法。

三、一个更真实的例子

import { defineStore } from 'pinia'

export const useTodos = defineStore('todos', {
  state: () => ({
    /** @type {{ text: string, id: number, isFinished: boolean }[]} */
    todos: [],
    /** @type {'all' | 'finished' | 'unfinished'} */
    filter: 'all',
    // 类型将自动推断为 number
    nextId: 0,
  }),
  getters: {
    finishedTodos(state) {
      return state.todos.filter((todo) => todo.isFinished)
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished)
    },
    /**
     * @returns {{ text: string, id: number, isFinished: boolean }[]}
     */
    filteredTodos(state) {
      if (this.filter === 'finished') {
        // 调用其他带有自动补全的 getters ✨
        return this.finishedTodos
      } else if (this.filter === 'unfinished') {
        return this.unfinishedTodos
      }
      return this.todos
    },
  },
  actions: {
    // 接受任何数量的参数,返回一个 Promise 或不返回
    addTodo(text) {
      // 你可以直接变更该状态
      this.todos.push({ text, id: this.nextId++, isFinished: false })
    },
  },
})