笔者:FL
修改日期:2024年2月11日
版本:2
vue3概述
什么是vue?
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架,它由尤雨溪创建,并且首次发布于 2014 年。Vue.js 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
Vue.js 作为一个渐进式框架,它允许你只用它的一部分功能,比如响应式数据绑定,而不需要使用它的路由或者状态管理功能。随着项目的发展,你可以逐渐引入更多的 Vue.js 功能,比如 Vuex 用于状态管理,Vue Router 用于页面导航等。
尽管vue.js更常被用于单页应用,它同样可以用来处理复杂的多页应用(MPA)。对于多页应用,每个页面都是一个独立的 HTML 文件,通常在页面之间切换时,需要从服务器加载新的 HTML 文档。Vue 可以通过服务器端渲染(SSR)或者将 Vue 组件独立地集成到不同的页面中来实现多页应用。
为什么使用vue3?
在企业中,常用的前端框架主要包括 React、Vue.js 、Express 和 Angular。这几个框架各有特点,但都非常流行,并且得到了广泛的企业应用:
Vue.js:
推荐理由:Vue.js 的设计注重简单性和易用性,对初学者非常友好。它的文档清晰,易于理解,而且中文社区活跃,中文资料丰富,对于中文母语者来说,学习起来会更加便利。在中国企业中更常用。
适合人群:适合那些希望快速看到成果、喜欢渐进式学习和逐步深入学习的初学者。
React:
推荐理由:React 是目前就业市场上需求最高的前端框架之一,它强调组件化和函数式编程的概念。学习 React 可以帮助你建立强大的组件设计和状态管理的思维方式。
适合人群:适合那些对前端开发有热情、愿意投入时间深入学习并希望在未来有更多就业机会的初学者。
Angular:
推荐理由:Angular 是一个由 Google 支持的完整框架,它提供了端到端的解决方案,包括工具、强大的功能集和最佳实践。学习 Angular 可以让你对前端开发的各个方面有更全面的了解。
适合人群:适合那些希望在一个严格和结构化的框架中工作,并且不介意较陡峭学习曲线的初学者。
Express:
推荐理由:Express 是一个轻量级的 Node.js 框架,用于构建后端服务。如果你对全栈开发感兴趣,学习 Express 可以帮助你理解服务器端的工作原理,并能够构建完整的前后端应用。
适合人群:适合那些对后端开发感兴趣、想要学习 Node.js 生态系统的初学者。
- 如果你是前端开发的初学者,我建议从 Vue.js 或 React 开始。这两个框架都非常流行,有大量的学习资源和社区支持。
- 对于前端就业有想法的初学者来说,学习 Vue.js 是一个很好的起点。不过,无论选择哪个框架,重要的是理解前端开发的核心理念和原则,这样就可以更容易地适应和学习新的技术和框架。
如何学习vue3?
学习 Vue 3 的步骤
基础知识:
- 确保你熟悉 HTML、CSS 和 JavaScript 基础。
官方教程:
- 阅读Vue 3的官方文档,了解其核心概念和特性。
- 完成 Vue 3 的官方教程,创建一个基本的 Vue 3 应用。
vue3教程:
- 官网:https://vuejs.org/
- 中文官网:https://cn.vuejs.org/
- 中文官网vue3:https://v3.cn.vuejs.org/
- 菜鸟教程:https://www.runoob.com/vue3/vue3-tutorial.html
- W3CSchool: https://www.w3cschool.cn/vuejs3/
视频教程推荐:
实践项目:
- 通过构建小型项目来实践 Vue 3 的知识,例如待办事项列表、天气应用等。
深入了解:
- 学习 Vue 3 的高级特性,如 Composition API、Teleport、Suspense 、异步组件等。
- 理解 Vue 3 的响应式原理和虚拟 DOM。
生态系统:
- 学习 Vue 3 生态系统的工具,如 Vuex、Vue Router、Vite 等。
案例研究:
- 分析开源的 Vue 3 项目,理解它们是如何设计和实现的。
- 仿照开源项目,自己实现一个小项目。
构建 Vue 3 项目时的编程思想
- 组件化思想:
- 将用户界面拆分为可复用的组件,每个组件负责界面的一个部分,这样可以提高开发效率和代码的可维护性。
- 模块化:
- 将代码按照功能模块进行组织,便于管理和重用。
- 响应式编程:
- 利用 Vue 3 的响应式系统来管理状态变化,确保界面与状态同步更新。
- 状态管理:
- 使用 Vuex 或 Composition API 的
reactive
、ref
等来管理全局状态,保持状态的一致性和可追踪性。
- 使用 Vuex 或 Composition API 的
- 路由管理:
- 使用 Vue Router 管理应用的路由,实现单页应用的无刷新页面切换。
- 类型安全*:
- 考虑使用 TypeScript 来增强项目的类型安全,减少运行时错误。
- 代码分割*:
- 利用现代打包工具(如 Vite 或 Webpack)进行代码分割,优化应用的加载性能。
- 测试*:
- 编写单元测试和端到端测试,确保代码的可靠性和稳定性。
- 最佳实践:
- 遵循 Vue 3 的最佳实践和编码规范,保持代码的一致性和可读性。
- 持续集成/持续部署(CI/CD):
- 使用 CI/CD 流程来自动化测试和部署,提高协作效率和项目的迭代速度。
项目搭建和工具
NPM使用
安装Node.js
- 安装了15.0以上的Node.js,如果你还没安装node.js,请自行搜索相关教程进行安装。
- 安装node.js可以使用其自带的npm包管理工具,后续你还会发现其更大的用处。
在命令行下查看node.js版本
node -v
NPM介绍
- 前端依赖安装工具,使用前需要先安装Node.js
常用指令
npm install //安装pakage.json中所有依赖
使用国内镜像
-
直接使用npm下载速度很慢,使用镜像进行加速
-
常用国内镜像如下:
npm 官方原始镜像网址是:https://registry.npmjs.org/
淘宝 NPM 镜像:https://registry.npm.taobao.org
阿里云 NPM 镜像:https://npm.aliyun.com
腾讯云 NPM 镜像:https://mirrors.cloud.tencent.com/npm/
华为云 NPM 镜像:https://mirrors.huaweicloud.com/repository/npm/
网易 NPM 镜像:https://mirrors.163.com/npm/
中科院大学开源镜像站:http://mirrors.ustc.edu.cn/
清华大学开源镜像站:https://mirrors.tuna.tsinghua.edu.cn/
腾讯,华为,阿里的镜像站基本上比较全
- 使用镜像下载
npm install -g 要下载的依赖 --registry=镜像网址
例如:
npm install -g @vue/cli --registry=https://mirrors.huaweicloud.com/repository/npm/
- 或者设置全局镜像,设置后可以直接下载依赖
npm config set registry 镜像地址
例如:
npm config set registry http://registry.npm.taobao.org/
npm install -g @vue/cli
创建和管理项目
Vue Cli创建项目
介绍:
- 官方提供构建工具,通常称为脚手架
Vue CLI的优势:
官方支持:Vue CLI是由Vue官方团队维护的,确保了与Vue生态系统的兼容性和最佳实践。
插件和预设:Vue CLI拥有一个庞大的插件生态系统,允许开发者添加额外的功能和工具,如Babel、TypeScript、ESLint、Prettier等。
项目脚手架:Vue CLI可以生成项目脚手架,包括预配置的目录结构和必要的文件,节省了手动设置的时间。
图形化界面:Vue CLI提供了一个图形化界面,允许开发者通过直观的操作来创建和管理项目。
易于更新:Vue CLI可以通过npm进行更新,确保开发者始终使用最新版本。
- 安装
# 全局安装 (推荐)
npm install -g @vue/cli
# 项目(局部)安装
npm install @vue/cli
如果下载速度慢,使用cpnm
# 安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 检查版本
cnpm -v
cnpm install -g @vue/cli
使用:
- 创建
vue create 项目名
创建项目
选择第三个,进行手动创建
注意:
- 如果是直接使用命令行,必须使用管理员权限打开
- 如果使用HbuildX,可以直接使用软件提供的模板(虽然会和自己创建的有点不一样)
- 如果使用vscode,需要用管理员权限打开,并将shell的当前执行策略改为
RemoteSigned
(1)以管理员身份运行VSCode
(2)执行命令:get-ExecutionPolicy(取得shell的当前执行策略)
显示Restricted(表示状态是禁止的),如果显示RemoteSigned就不需要更改
(3)执行命令:set-ExecutionPolicy RemoteSigned
(4)执行命令:get-ExecutionPolicy,显示RemoteSigned
- 选择特点
将Linter取消(按空格取消),初学者不需要,其他依赖可以后面再在package.json添加。
- 选择vue版本(根据需要选择),我们选择vue3
- vue2具有简单易学、易于上手的特点,适合新手快速学习搭建基础项目。
- Vue 3 提供了更好的性能和更小的打包体积。因此,Vue 3 更适合搭建复杂的企业级项目,也是企业主流。
- 不过,Vue 2 和 Vue 3 之间有很大的兼容性,因此开发者可以在项目中同时使用两个版本。如果团队已经熟悉 Vue 2,那么可以继续使用 Vue 2 搭建项目。如果团队想要尝试新的特性和优化,那么可以尝试使用 Vue 3。
- 选择依赖文件位置(package.json即可)
-
是否要将前面的设置设为快照(N)
-
选择包管理工具(yarn/pnpm/npm)
npm由于其与Node.js的捆绑,仍然是使用最广泛的包管理工具。
然而,许多开发者和团队也转向了Yarn或pnpm,因为它们在性能和依赖管理方面提供了优势。
Yarn因其易用性和附加功能在企业级应用中获得了大量支持。
pnpm由于其高效的磁盘空间管理和安装速度,也获得了社区的关注和采用。
初学者使用npm就行,后续可以根据自己需求选择。
- 等待创建成功
记住三个指令:
cd 项目名 # 跳转到项目目录下
npm run serve # 运行项目
npm install # 安装依赖
使用yarn
安装yarn
npm install -g yarn
或
cnpm install -g yarn
安装vite
yarn add -D vite
初始化
yarn init -y
添加打包工具
# vite是开发工具,需要-D
yarn add vite -D
# vue不需要
yarn add vue
创建vue工程
yarn create vue
使用什么软件
IDE
你可以使用vscode编写大多数前端代码,包括vue。但是需要安装插件,如果使用其编写vue3,需要安装插件Volar、Vue VSCode Snippets和Vite。
除了vscode,你还可以使用IDEA,HBuilderX等软件。其中HBuilderX是国产的,根据国产替代趋势,HBuilder以后可能用的比较多。
你可以随意选择你熟悉的软件。推荐HBuilderX,开箱即用,无需配置,且进阶uniapp也可以使用。
浏览器
给浏览器安装一个vue.js devtools
,方便调试。
第一个vue3程序-HelloWorld
vue代码可以通过以下两种方式编写,第一种方式适合做几个简单的实例代码,但是实际开发中多使用第二、三种直接在项目中使用vue。
直接使用vue*
- 创建.html文件
- 引入vue.js脚本(使用网络CDN或自己下载)
- 在body标签中使用vue语法
下面是示例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World</title>
<!--引入vue.js脚本-->
<script src="https://cdn.staticfile.org/vue/3.0.5/vue.global.js"></script>
<style>
/* 定义根元素的样式 */
#root {}
</style>
</head>
<body>
<div id="root">
</div>
<!-- 在script里写vue代码 -->
<script>
// 创建一个根组件,在vue3中组件就是一个普通的js对象
const Root = {
data() {
return {
msg: '-by FL' //data方法返回的对象,其中的属性会自动添加到组件实例中
}
},
// 在模板中需要通过{{属性名}}的模板语法访问组件中的属性
template: "<h1>Hello World!!!<small>{{msg}}</small></h1>" //定义组件模板,希望在网页中显示什么
}
// 创建vue实例app
const app = Vue.createApp(Root)
// 在使用 mount() 挂载应用时,该组件被用作渲染的起点。
app.mount('#root') //将实例app挂载到页面中的#root元素上
</script>
</body>
</html>
实例效果如下:
template
是模板,它决定了组件的最终样子如果在组件中定义了template,则会优先使用template作为模板,同时根元素中的所有内容都会被替换;否则就会将根元素的innerHTML作为模板使用
间接引用(初始)
-
使用Vue CLI创建一个vue3项目。项目初始已经有了vue.js等依赖,你不需要使用script标签导入。如果是其他自己下载的库,例如axios,你需要在main.js或组件中使用
import
语句导入。 -
打开APP.vue(根组件)
初始情况如下,你会发现.vue文件与.html文件有点像,但是又有所不同:
<!-- template标签定义了组件的模板,类似于HTML文件中的body部分,但具有Vue.js特有的指令,例如下面HelloWorld标签中的msg -->
<template>
<img alt="Vue logo" src="./assets/logo.png">
<!-- 3.使用自定义组件,msg属性用来传递一个欢迎信息 -->
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script>
// script标签内通常包含组件的JavaScript代码
// 1.导入HelloWorld组件
import HelloWorld from './components/HelloWorld.vue'
// 默认导出的是一个Vue组件选项的对象
export default {
name: 'App',
components: {
// 2.注册局部组件,使其可以在模板中使用
HelloWorld
}
// 在这里可以定义数据、方法、计算属性等
}
</script>
<style>
/* style标签内定义了组件的样式,这里可以使用CSS、SCSS、SASS等预处理器 */
/* #app选择器定义了根元素的样式 */
#app {
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
标签包含了Vue组件的HTML结构。与HTML不同,这里可以使用Vue特定的语法,如指令、组件等。<script>
标签包含了Vue组件的JavaScript代码。在这里定义了组件的数据、方法、计算属性、生命周期钩子等。<style>
标签包含了Vue组件的样式。与HTML中的<style>
标签相同,但它通常只应用于当前组件,避免全局样式的冲突。.vue文件一般都包括上面三个标签,其中
<template>
标签是必须的。通过阅读App.vue这个根组件的代码,你可以知道vue组件之间是可以相互引用的。不过我们一般不会引用太多层,多在App.vue中引用其他组件。
如果你想编写自己的组件,你可以在/src/components目录下编写.vue文件,然后在根组件App.vue中注册使用。不过你需要在App.vue删除无用的组件,防止产生混淆。
其实,vue也可以处理多页面,不过会需要额外的配置,这里暂不介绍。
- 当你打开/public/index.html和/src/main.js,你会发现这种使用vue的方式和第一种有很多相似之处。
<!DOCTYPE html>
<html lang="">
<head>
...
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
import { createApp } from 'vue'
import App from './App.vue'
// createApp(App).mount('#app')
// app:Vue的实例对象
// 在一个vue项目中,有且只有一个vue的实例对象
const app = createApp(App)// App:根组件
// 挂载应用
app.mount('#app')
间接引用(进阶)
-
使用
<script setup>
后,不再需要 export default 对象来定义组件选项,使得代码更加简洁。 -
使用
<style scoped>
可以确保样式只应用于当前组件,避免样式冲突,维护了组件的独立性。
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<style scoped>
#app {
color: #2c3e50;
margin-top: 60px;
}
</style>
总结
其实第二、三种方式就是为了模块化和组件化,将第一种方式进行了拆分。
浏览器本身并不直接解析 .vue 文件。.vue 文件是 Vue.js 单文件组件(SFCs)的格式,它需要通过 Vue.js 的构建工具链(如 Vue CLI、Vite 或 webpack)进行预处理和编译,最终生成浏览器可以解析的 JavaScript、CSS 和 HTML 代码。
根组件App.vue在被解析后被挂载到index.html中的#app中。
第一种方式只适合老项目,第二种方式常用于vue2,第三种方式常用于vue3。具体规则后面讲解。
在vue3中,普通代码使用第三种方式较为简便,第二种方式则用来配置。
HelloWorld项目分析
项目结构
初始:
完整:
my-vue3-project/
|-- node_modules/ # 存放项目依赖的第三方包
|-- public/ # 存放不会经过Webpack处理的静态资源文件,如index.html、favicon.ico等
| |-- index.html # 主页面HTML文件
| |-- favicon.ico # 页面图标(浏览器栏显示的小图标)
|-- src/ # 源代码目录
| |-- assets/ # 存放静态资源,如图片、样式、字体等
| |-- components/ # 存放可复用的Vue组件
| |-- views/ # 存放页面级别的Vue组件
| |-- App.vue # 根组件,所有页面都在这个组件内渲染
| |-- main.js # 入口文件,用于创建Vue实例并挂在到#app
| |-- router/ # 存放Vue Router相关配置
| | |-- index.js # 路由配置文件
| |-- store/ # 存放Vuex相关文件(如果使用Vuex)
| | |-- index.js # Vuex配置文件
| |-- api/ # 存放与后端接口交互的代码
| |-- utils/ # 存放工具函数
| |-- styles/ # 存放样式文件,如全局样式、变量等
| |-- hooks/ # 存放Vue自定义钩子函数
| |-- plugins/ # 存放Vue插件
| |-- layouts/ # 存放布局组件
| |-- services/ # 存放服务层的代码,如API调用
| |-- directives/ # 存放自定义指令
| |-- filters/ # 存放自定义过滤器(Vue 3中较少使用)
|-- .gitignore # 指定Git将忽略的文件和目录
|-- babel.config.js # Babel配置文件
|-- package.json # 项目描述文件,包含依赖、脚本等信息
|-- README.md # 项目说明文档
|-- vue.config.js # Vue CLI配置文件(如果使用Vue CLI)
可以自己添加的文件和目的:
src/constants/:存放常量文件,如API地址、默认配置等。
src/types/:存放TypeScript类型定义文件(如果使用TypeScript)。
src/mock/:存放模拟数据,用于前端独立于后端开发。
src/theme/:存放项目的主题样式,如颜色、字体等。
src/enums/:存放枚举类型定义。
src/locales/:存放国际化(i18n)的翻译文件。
src/tests/:存放单元测试和集成测试文件。
src/vendors/:存放第三方库或插件的定制代码。
.env、.env.local等:环境变量配置文件,用于不同环境下的配置管理。
常用文件
App.vue
App.vue 是 Vue 3 项目的根组件,它是整个应用程序的入口组件。通常,你会在这里定义应用的布局和结构。App.vue 文件通常包含三个部分:模板(template)、逻辑(script)和样式(style)。
<!-- App.vue -->
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
- 模板(template):使用 HTML-like 语法定义组件的结构。在这里,你可以使用 Vue 的指令,如 v-bind、v-model 等。
- 逻辑(script):使用 JavaScript-like 语法,通常在这里导入其他组件,定义组件的数据、方法、计算属性、生命周期钩子等。export default 语句导出的是一个 Vue 组件选项的对象。
- 样式(style):定义组件的样式。你可以使用 CSS,也可以使用预处理器如 SCSS 或 LESS。
<style>
标签的 scoped 属性确保样式只应用于当前组件。
package.json
package.json 是一个 JSON 文件,位于项目的根目录中,它定义了项目所需的依赖项、脚本、配置和其他元数据。这个文件是由 npm(Node Package Manager)管理的,用于帮助管理和组织项目。
{
"name": "my-vue3-project",
"version": "1.0.0",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^3.0.0",
"vue-router": "^4.0.0"
},
"devDependencies": {
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0",
"vue-template-compiler": "^2.6.10"
}
}
- name:项目的名称。
- version:项目的版本号。
- scripts:定义了一组可以运行的 npm 脚本,如 serve 和 build。
- dependencies:项目运行时所需的依赖项。
- devDependencies:项目开发时所需的依赖项,如构建工具、测试框架等
main.js
main.js 是 Vue 3 项目的入口文件,它负责创建 Vue 应用实例并挂载到 DOM 元素上。在这个文件中,你会初始化 Vue 应用,配置全局属性,比如全局组件、插件等。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
- createApp:Vue 3 提供的函数,用于创建一个新的 Vue 应用实例。
- App:导入的 App.vue 根组件。
- router:Vue Router 实例,用于管理应用的路由。.use(router) 是用来安装 Vue Router 插件的。
- mount:将 Vue 应用实例挂载到 HTML 中的一个元素上,这里是 #app。
Vue基础
vue标签
模板<template>
Vue 3 中的 <template>
标签是一个用于包裹多个子节点的容器,它本身不会渲染任何 DOM 元素,而是用于组织和管理组件的结构。在 Vue 3 中,<template>
标签提供了一些新的特性和改进,使得它在构建复杂的组件时更加灵活和高效。
模板语法
在 <template>
标签中,可以使用多种模板语法,这里只介绍最常用的插值语法{{}}
,其他模板语法后面介绍。
下面示例中,使用{{ }}
双大括号来绑定了文本内容。
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue 3!'
}
}
}
</script>
模板引用
在 Vue 3 中,可以通过 ref 属性为<template>
或组件元素指定一个引用名称,以便在脚本中引用真实的 DOM 元素或组件实例。这种引用非常有用,尤其是当你需要直接操作 DOM 或者访问组件实例的方法和属性时。
基本步骤:
- 在
<template>
中给元素添加 ref 属性。 - 在组件的 setup 函数中,使用 ref() 或 onMounted 钩子来访问引用。
<template>
<div ref="container">Hello, Vue 3!</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const container = ref(null)
onMounted(() => {
console.log(container.value) // 访问真实的 DOM 元素
})
return {
container
}
}
}
</script>
动态组件
component
是一个动态组件,其最终以什么标签呈现由is属性决定。- is属性除了可以是html自定义的标签,还可以是引入的组件
<!-- App.vue -->
<template>
<component is="h2">111</component>
<component is="h1">111</component>
<component is="div">111</component>
<component :is="A"></component>
</template>
<script setup>
import A from './components/A.vue'
import B from './components/B.vue'
</script>
插槽(slots)
在 Vue 3 中,插槽(Slots)是一种非常强大的功能,它允许我们创建可复用的组件,同时又能让这些组件的内容和结构具有很高的灵活性。
插槽让我们可以在组件的模板中预留一个位置,使得使用该组件的地方可以决定这些位置的具体内容。即插槽可以实现在父组件中指定子组件的内容(innerHTML)。
默认插槽
在子组件中,我们可以使用 <slot>
元素来定义一个插槽。
<!-- 子组件 A.vue-->
<template>
<div>
<slot>默认内容</slot> <!-- 默认插槽 -->
<!-- 默认插槽里面可以有默认内容,如果父组件定义了该组件没有传递东西,就会显示slot中的默认内容 -->
</div>
</template>
在父组件中,我们可以将内容传递给这个插槽。
<!-- App.vue -->
<template>
<A></A>
<A>父组件的内容</A>
<A><span>父组件的内容</span></A>
<!-- 注意,这里B组件不是A的子组件,A、B组件都是App.vue的子组件,而A、B组件互为兄弟组件 -->
<A><B></B></A>
</template>
<script setup>
import A from './components/A.vue'
import B from './components/B.vue'
</script>
具名插槽
如果组件中有多个插槽,我们可以通过给 <slot>
元素添加 name
属性来定义具名插槽。
<!-- 子组件 A.vue-->
<template>
<div>
<slot name="header"></slot>
<slot ></slot> <!-- 这是默认插槽的内容 -->
<slot name="footer"></slot>
</div>
</template>
在父组件中,我们可以通过 v-slot
指令(在 Vue 3 中,可以使用新的 #
符号作为 v-slot
的简写)来指定内容对应的具名插槽。
<!-- App.vue -->
<template>
<!-- 如果直接往子组件的插槽传值,只会修改子组件的默认插槽 -->
<A>
这是默认插槽的内容
</A>
<A>
<template v-slot:header>
<h1>这是头部内容</h1>
</template>
<template v-slot:default>
<p>这是默认插槽的内容</p>
</template>
<template #footer>
<h2>这是底部内容</h2>
</template>
</A>
</template>
<script setup>
import A from './components/A.vue'
</script>
作用域插槽
作用域插槽允许子组件向父组件传递数据,使得父组件可以定义一个模板来接收子组件传递的数据。
<!-- 子组件 Child.vue-->
<template>
<div>
<slot :user="user"></slot>
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: '张三',
age: 30
}
}
}
}
</script>
在父组件中,我们可以通过 v-slot
指令的值来定义一个插槽 prop,然后使用这个 prop。
<!-- 父组件 App.vue-->
<script setup>
import A from './components/Child.vue'
</script>
<template>
<Child>
<template v-slot="slotProps">
<p>姓名:{{ slotProps.user.name }}</p>
<p>年龄:{{ slotProps.user.age }}</p>
</template>
</Child>
</template>
解构插槽 Prop
Vue 3 允许我们直接在 v-slot
指令中使用解构赋值,使得代码更加简洁。
<!-- 父组件 -->
<template>
<ChildComponent>
<template v-slot="{ user }">
<p>姓名:{{ user.name }}</p>
<p>年龄:{{ user.age }}</p>
</template>
</ChildComponent>
</template>
Teleprot
当需要显示模态框或弹出层时,我们可能会在Vue组件的模板中创建模态对话框,并将其作为DOM树的一部分。这意味着模态对话框将作为其父组件的一部分渲染。这样,如果页面中其他元素具有较高的z-index,可能会覆盖模态框。此外,可能会受到其他组件样式的干扰,需要额外的CSS规则来确保模态框的正确显示。
vue中,为我们提供了
Teleprot
,方便设置模态框或弹出层。
Vue 3中的<Teleport>
是一个内置的组件,它的主要功能是允许开发者将子组件渲染到DOM树的其他位置,而不是组件实际所在的位置。这通常被称为“传送”或“瞬移”。具体来说,<Teleport>
接受一个to属性,该属性的值是一个CSS选择器,表示目标位置,Vue会确保<Teleport>
的内容渲染到该目标位置。
下面是使用<Teleport>
的一个基本示例,实现当点击登录按钮后,会弹出登录的模态框:
<!-- App.vue -->
<script setup>
import {
ref
} from 'vue';
const showModal = ref(false);
const username = ref('');
const password = ref('');
function submitForm() {
// 处理表单提交逻辑
console.log('用户名:', username.value);
console.log('密码:', password.value);
showModal.value = false; // 提交后关闭模态框
}
</script>
<template>
<div id="app">
<button @click="showModal = true">登录</button>
<Teleport to="body">
<div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
<div class="modal-content">
<h2>登录</h2>
<form @submit.prevent="submitForm">
<div>
<label for="username">用户名:</label>
<input type="text" id="username" v-model="username" required>
</div>
<div>
<label for="password">密码:</label>
<input type="password" id="password" v-model="password" required>
</div>
<button type="submit">提交</button>
</form>
<button @click="showModal = false">关闭</button>
</div>
</div>
</Teleport>
</div>
</template>
<style scoped>
/* modal-overlay用于创建一个覆盖整个屏幕的背景,并且当点击背景时,会关闭模态框。 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
/* modal-content内部包含了登录表单和一个关闭按钮 */
.modal-content {
background: white;
padding: 20px;
border-radius: 5px;
width: 300px;
}
.modal-content h2 {
margin-top: 0;
}
.modal-content div {
margin-bottom: 10px;
}
</style>
异步组件
逻辑script
★
用于定义组件的脚本部分。它通常包含以下内容:
- 导出默认对象: 使用 export default 语句来定义组件的选项,如 data, methods, computed, props, setup 等。
- 定义组件数据: 通过 data 函数返回一个对象,该对象包含所有组件实例的数据。
- 声明方法: 在 methods 对象中定义函数,这些函数可以操作数据并处理用户输入。
- 计算属性: computed 用于声明那些其值取决于其他数据属性的响应式数据。
- 生命周期钩子: Vue 提供了一系列生命周期钩子函数,如 mounted, created, updated 等,用于在组件的不同生命周期阶段执行代码。
两种API
Vue.js 3.0 引入了许多新特性和优化,其中包括对 Composition API (组合式 API)的添加,它作为新的核心特性与原有的 Options API(选项式 API ) 一起工作。
- 选项式 API 是 Vue.js 传统的组件写法,适合简单的组件和初学者。选项式 API 的优点是结构清晰,易于理解和上手。但是,当组件变得复杂时,相关的逻辑会分散在不同的选项中,使得代码组织和重用变得困难。
- 组合式 API 是 Vue 3 引入的新特性,提供了更灵活的代码组织和重用方式,可以更容易地将相关的逻辑组合在一起,并且可以跨组件重用这些逻辑。这对于编写大型和复杂的组件尤其有用。
开发者可以根据自己的需求选择使用 Options API 或 Composition API,两者可以共存于同一个组件中。这使得开发者可以渐进式地升级现有项目到 Vue 3,同时享受 Composition API 带来的灵活性。
选项式API*
选项式 API 是 Vue.js 从一开始就有的组件写法。在这种写法中,你通过填充 (options) 对象的方式来描述组件的逻辑,这个对象包含了如 data, methods, props 等属性和生命周期钩子。每个属性都对应组件的一个方面。
属性
一个组件的代码被组织为一系列选项,如 data、methods、computed、watch、lifecycle hooks 等。每个选项都有其特定的用途
data
- data:
- data是一个函数,返回一个对象,该对象包含Vue实例的所有响应式数据。该对象会被vue所代理。
- Vue会自动将data中的属性转换为响应式的,当数据变化时,视图会自动更新。
- data的属性必须通过return语句返回
示例(在App.vue添加以下代码):
<template>
<h1>{{msg}}</h1>
<hr />
<h2>全民制作人大家好,我是{{stu.name}},喜欢{{stu.hobby.hobby1}},{{stu.hobby.hobby2}},{{stu.hobby.hobby3}},{{stu.hobby.hobby4}}
</h2>
</template>
<script>
export default {
data() {
return {
msg: "两年半",
stu: {
name: "cxk",
age: 18,
gender: "女",
hobby: {
hobby1: "唱",
hobby2: "跳",
hobby3: "rap",
hobby4: "篮球",
}
}
}
}
}
</script>
<style>
</style>
效果如下:
methods
- methods:
- methods包含一系列方法,这些方法可以在模板中通过指令如v-on来调用,也可以在组件的实例中通过this来调用。
- 这些方法中的this上下文指向当前的Vue实例。
<template>
<h1>{{ count }}</h1>
<hr />
<!-- 在模板中绑定事件处理器时,如果方法后面跟有括号,该表达式会被当作 JavaScript 表达式计算,这意味着 increament() 会被立即调用,而不是作为一个函数被引用 -->
<button @click="increament">+1</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increament() {
console.log(this.count) // 使用 this 引用 count
this.count++; // 使用 this 更新 count 的值
}
}
}
</script>
<style>
</style>
computed
计算属性和侦听器都是响应式数据变化的利器,但它们的使用场景有所不同。
- 计算属性适用于同步操作,当需要根据一个或多个数据属性计算出一个新值时,计算属性是最佳选择。
- 而侦听器适用于异步操作或复杂逻辑,当你需要在数据变化时执行副作用(如Ajax请求、数据存储等)时,侦听器更为合适。
- computed:
- computed包含一系列计算属性,这些属性是基于它们的依赖进行缓存的。
- 只有当依赖的数据发生变化时,计算属性会重新计算,即computed会对数据进行缓存。而method每次组件重新渲染都会调用。
- 计算属性默认只有getter,但也可以提供setter。尽量只使用computed读取属性(getter),不要用它修改属性(setter),例如像method中修改count的值。
- 计算属性是通过函数返回结果的,所以在computed中可以添加各种逻辑。这是computed与data最大的不同。
<template>
<h1>你的成绩是{{ count }},{{rank}}</h1>
<hr />
<!-- 在模板中绑定事件处理器时,如果方法后面跟有括号,该表达式会被当作 JavaScript 表达式计算,这意味着 increament() 会被立即调用,而不是作为一个函数被引用 -->
<button @click="increament">+10</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increament() {
console.log(this.count)
this.count += 10;
}
},
computed: {
rank() {
if (this.count >= 80) {
return "优秀";
} else if (this.count >= 60) {
return "及格";
} else {
return "不及格"
}
}
}
}
</script>
<style>
</style>
侦听器
侦听器
用于观察和响应Vue实例上的数据变动。当你有一些数据需要随着其它数据变动而变动时,或者需要在数据变化时执行异步或开销较大的操作时,侦听器非常有用。
基本用法:
export default {
data() {
return {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
};
},
watch: {
// 监听question的变化
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf('?') > -1) {
this.getAnswer();
}
}
},
methods: {
getAnswer() {
// 异步操作或开销较大的操作
}
}
};
深度侦听和立即执行:
export default {
data() {
return {
obj: {
key: 'value'
}
};
},
watch: {
obj: {
//使用deep: true可以侦听对象内部值的变化,immediate: true可以让侦听器立即执行一次。
deep: true, // 深度侦听
immediate: true, // 立即执行
handler(newValue, oldValue) {
// 处理变化
}
}
}
};
- props:
- props用于定义组件可以接收的外部参数。
- 父组件可以通过属性的方式将数据传递给子组件。
- emits:
- emits是一个包含所有父组件可以监听的事件名称的数组。
- 它用于定义组件可以触发的事件,以便父组件可以监听这些事件。
生命周期钩子
- 生命周期钩子(如 created、mounted、updated、destroyed)用于在组件的不同生命周期阶段执行代码。
- 这些钩子函数在组件的不同生命周期阶段被调用,用于执行特定的操作。
常用生命周期钩子:
- beforeMount:
- 在组件挂载到DOM之前调用。此时,模板已经编译完成,但是尚未挂载到页面上。
- 可以在这个钩子中进行一些数据请求或者最后的准备工作。
- mounted:
- 在组件挂载到DOM之后调用。此时,组件已经创建好了DOM结构,可以访问到DOM元素。
- 可以在这个钩子中执行依赖于DOM的操作,如集成第三方库或初始化页面。
- beforeUpdate:
- 在组件数据更新之前调用,但是仅仅在数据变化时调用。
- 可以在这个钩子中执行一些逻辑,如手动移除已添加的事件监听器。
- updated:
- 在组件数据更新之后调用,同样仅仅在数据变化时调用。
- 可以在这个钩子中执行依赖于更新后的DOM的操作。
- beforeUnmount:
- 在组件卸载之前调用,此时实例仍然完全可用。
- 可以在这个钩子中执行一些清理工作,如取消定时器、解绑事件监听器。
- unmounted:
- 在组件卸载之后调用。当这个钩子被调用时,组件的所有指令都被解绑,所有的事件监听器都被移除。
- 可以在这个钩子中执行最终的清理工作。
<template>
<div id="app">
{{ message }}
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue!'
};
},
beforeMount() {
console.log('beforeMount: 组件挂载之前');
},
mounted() {
console.log('mounted: 组件挂载之后');
},
beforeUpdate() {
console.log('beforeUpdate: 数据更新之前');
},
updated() {
console.log('updated: 数据更新之后');
},
beforeUnmount() {
console.log('beforeUnmount: 组件卸载之前');
},
unmounted() {
console.log('unmounted: 组件卸载之后');
}
};
</script>
组合式API
- 组合式API (Composition API)是我们使用vue3主要的API风格,vue2也支持。
- 组合式 API 的核心是 setup 函数,它在组件创建之前执行,允许开发者定义组件的响应式数据和函数。setup 函数返回的对象中的属性和方法可以在组件的模板中使用。
- 组合式 API 的优点是可以按逻辑组织代码,而不是按选项类型。这有助于提高代码的可读性和可维护性,尤其是在处理复杂组件时。它还提供了更好的类型支持,使得与TypeScript集成更加容易。
- Composition API 提供了一系列生命周期钩子函数,如 onMounted, onUpdated 等,它们与 Options API 中的生命周期钩子相对应。
传统的 setup 选项
- setup是一个新的组件选项,它是一个在组件创建之前执行的函数。
- 在setup中,你可以定义响应式数据、计算属性、侦听器、生命周期钩子等。
- setup函数中定义的属性和函数可以在模板中直接使用。
- 优势:
- 明确返回值:setup 函数需要明确返回一个对象,这个对象中的属性和方法将会暴露给模板使用。这种方式使得数据的来源和使用更加清晰。
- 更好的类型支持:对于使用 TypeScript 的开发者来说,这种方式可以提供更好的类型支持,因为返回对象的类型可以很容易地被推导和验证。
- 劣势:
- 稍微繁琐:需要显式地定义和返回所有需要在模板中使用的属性和方法,对于简单的组件来说,这可能显得有些繁琐。
<template>
<h1>{{msg}}</h1>
<hr />
<h2>全民制作人大家好,我是{{stu.name}},喜欢{{stu.hobby.hobby1}},{{stu.hobby.hobby2}},{{stu.hobby.hobby3}},{{stu.hobby.hobby4}}
</h2>
</template>
<script>
import {
reactive
} from "vue"
export default {
setup() {
let msg = "两年半";
let stu = reactive({
name: "cxk",
age: 18,
gender: "女",
hobby: {
hobby1: "唱",
hobby2: "跳",
hobby3: "rap",
hobby4: "篮球",
}
});
return {//需要通过return暴露,否则外部无法访问
msg,
stu
}
}
}
</script>
<style>
</style>
<script setup>
<script setup>
是 Vue 3.2 引入的一种语法糖,它进一步简化了 Composition API 的使用。在<script setup>
中,不需要显式地定义 setup 函数,也不需要返回任何内容。所有在<script setup>
中声明的顶层变量、函数和导入的组件都会自动暴露给模板。
- 优势:
更简洁的语法:减少了模板中需要显式返回对象的样板代码,使得组件的编写更加简洁。
更好的开发体验:提供了更接近于原生 JavaScript 的编写方式,使得开发者可以更加专注于业务逻辑的实现。- 劣势:
潜在的命名冲突:由于所有顶层变量都会自动暴露,这可能导致潜在的命名冲突,尤其是在大型项目中。
类型支持稍逊:虽然 Vue 3.2 的<script setup>
也提供了很好的类型支持,但对于一些复杂的类型推导场景,可能不如传统 setup 函数直观。
<script setup>
import {
reactive
} from "vue"
let msg = "两年半";
let stu = reactive({
name: "cxk",
age: 18,
gender: "女",
hobby: {
hobby1: "唱",
hobby2: "跳",
hobby3: "rap",
hobby4: "篮球",
}
});
</script>
<template>
<h1>{{msg}}</h1>
<hr />
<h2>全民制作人大家好,我是{{stu.name}},喜欢{{stu.hobby.hobby1}},{{stu.hobby.hobby2}},{{stu.hobby.hobby3}},{{stu.hobby.hobby4}}
</h2>
</template>
<style>
</style>
响应式代理
-
响应式代理是 Vue 3 中实现响应式数据的基础,它通过代理对象的方式,使得数据的读取和修改能够触发视图的自动更新,从而实现了数据与界面的双向绑定。
-
reactive()
- 返回一个对象的响应式代理(Proxy)。默认返回的是一个深层响应式对象,
- 可以使用shallowRective()创建一个浅层响应式对象。
- 不能返回原始值的响应式对象,只能返回对象的
-
ref()
- 会自动将任意属性和对象转换为响应式代理(Proxy)
- 被转换的属性和对象在script标签中需要通过value访问,而在模板中会被自动解包,不需要通过value访问
<script setup>
import {
ref, computed
} from "vue";
let count = ref(0)
console.log(count.value)
//obj1是代理对象
const obj1 = ref({
name: "张三",
age: 17
})
console.log(obj1.value.age)
//obj2不是代理对象,单是其属性是代理对象
const obj2 = {
name: ref("张三"),
age: ref(17)
}
console.log(obj2.age.value);
//对obj2进行解构
const {
name,
age
} = obj2
//使用计算属性
const newAge = computed(() => {
return age.value +10;
})
</script>
<template>
<h2>{{obj1.age+1}}</h2>
<!-- name不是顶层响应式对象,所以不能自动解包 -->
<h2>{{obj2.age.value+1}}</h2>
<!-- 解构后的对象会自动解包 -->
<h2>{{age+1}}</h2>
<h3>{{count}}</h3>
</template>
样式<style>
<style scoped>
- 如果使用
<style>
,设置的是全局样式。为了防止样式冲突,我们通常使用<style scoped>
。 - 如果想在
<style scoped>
中设置全局样式,可以使用:deep(过滤器)
设置,例如:#app :deep(h2)
就是设置app下的所有h2 - 你还可以使用
:global(过滤器)
设置全局样式 - 你也可以在根组件中既使用
<style>
又使用<style scoped>
示例1(使用style):
<!-- App.vue -->
<template>
<A />
</template>
<script setup>
import A from './components/A.vue'
</script>
<style>
.sa {
color: blue;
background-color: #bfa;
}
</style>
<!-- A.vue -->
<script setup>
import {
ref
}
from 'vue'
const url = 'https://vuejs.org/'
const imgSrc = '/images/kun.png' //使用/public/images/kun.png无法访问,因为images前的/就是根目录public
const attrs = ref({
src: '/images/kun.png',
alt: 'kun plus',
style: 'width: 200px;'
})
</script>
<template>
<h2 class="sa">超链接:<a v-bind:href="url">Visit Vue.js</a></h2>
<img :=attrs />
</template>
示例2(使用style scoped):
<!-- App.vue -->
<template>
<A />
</template>
<script setup>
import A from './components/A.vue'
</script>
<style scope>
.sa {
color: blue;
background-color: #bfa;
}
</style>
<!-- A.vue -->
<script setup>
import {
ref
}
from 'vue'
const url = 'https://vuejs.org/'
const imgSrc = '/images/kun.png' //使用/public/images/kun.png无法访问,因为images前的/就是根目录public
const attrs = ref({
src: '/images/kun.png',
alt: 'kun plus',
style: 'width: 200px;'
})
</script>
<template>
<h2 class="sa">超链接:<a v-bind:href="url">Visit Vue.js</a></h2>
<img :=attrs />
</template>
- 注意,如果示例2
<template>
中只有一个根(标签),那么它也会变色。
当我们在组件中使用scoped样式时,vue会自动为组件中的所有元素生成一个随机的属性形如:data-v-7a7a37b1
生成后,所有的选择器都会在最后添加一个[data-v-7a7a37b1]
h1一>h1[data-v-7a7a37b11
.box1一>.box1[data-v-7a7a37b1]
注意:
- 随机生成的属性,除了会添加到当前组件内的所有元素上也会添加到当前组件引入的其他组件的根元素上
- 这样设计是为了,可以通过父组件来为子组件设置一些样式
<style module>
在 Vue 3 中,<style module>
是一种特殊的样式封装方式,它允许你将 CSS 类名模块化,以避免全局样式的冲突。当你在一个 <style>
标签上使用 module
属性时,所有的 CSS 类名都只在当前组件内部有效,并且每个类名都会被编译成一个唯一的字符串,以避免在不同的组件或页面中重复。
使用 <style module>
的好处包括:
- 局部作用域:样式只在当前组件内部有效,不会影响到其他组件或全局样式。
- 避免样式冲突:由于类名是唯一的,因此不会出现不同组件间样式相互覆盖的问题。
- 易于维护:模块化的样式使得组件更加自包含,便于理解和维护。
- 更好的重用性:可以在不同的组件中重用相同的 CSS 类名,而不用担心冲突。
- 动态样式:可以通过 JavaScript 动态绑定样式类,实现更加灵活的样式管理。
使用<style module>
的基本语法如下:
<template>
<div :class="$style.red">This text is red.</div>
</template>
<style module>
.red {
color: red;
}
</style>
在模板中,你需要使用 $style
对象来引用模块化的类名。$style
对象是由 Vue 自动创建的,它包含了所有在 <style module>
中定义的类名。
如果你想要给模块化的类名添加前缀,以避免潜在的冲突,你可以在 module
属性中指定一个前缀:
<style module="myPrefix">
.red {
color: red;
}
</style>
然后在模板中使用时,你需要引用带有前缀的样式对象:
<div :class="myPrefix.red">This text is red.</div>
区别
<style module>
和 <style scoped>
都是 Vue 中用于模块化和封装样式的特性,但它们的工作方式和应用场景有所不同。
<style scoped>
- 优势:
- 简单易用:通过添加 scoped 属性,可以很容易地将样式限制在当前组件的作用域内。
- 避免全局污染:自动为组件内的元素添加唯一的属性(如 data-v-123456),使得样式只应用于带有该属性的元素,避免影响到组件外的元素。
- 劣势:
- 不能继承:父组件的 scoped 样式不会应用到子组件中。
- 需要手动处理深层次选择器:如果需要影响到子组件的样式,需要使用 /deep/ 或 ::v-deep 样式穿透。
- 类名冲突:虽然减少了全局样式冲突,但在同一个组件内使用相同的类名仍然可能导致样式被覆盖。
- 应用场景:
- 当你需要确保组件的样式不会影响到其他组件时。
- 当你不需要在组件之间共享样式时。
<style module>
- 优势:
- 模块化:每个组件的样式都是独立的模块,不会与其他组件发生冲突。
- 类名唯一:每个类名都会被编译成唯一的字符串,即使不同组件使用了相同的类名也不会冲突。
- 易于重用:可以在不同的组件中重用相同的 CSS 类名。
- 动态绑定:可以通过 JavaScript 动态绑定样式类,实现更加灵活的样式管理。
- 劣势:
- 不能继承:与
<style scoped>
类似,父组件的模块化样式不会应用到子组件中。 - 性能开销:由于需要为每个类名生成唯一的字符串,可能会有一些性能开销。
- 不能继承:与
- 应用场景:
- 当你需要更细粒度的样式管理,确保样式不会泄漏到其他组件时。
- 当你需要在一个大型项目中管理大量样式,并且希望避免样式冲突时。
- 当你需要通过 JavaScript 动态绑定样式类时。
类与内联样式
三种方式:直接设置;通过class绑定;通过style绑定
<script setup>
const arr1 = ["cr", "bf"]
const arr2 = [{
cr: true
}, {
bf: false
}]
const style = {
color: "red",
backgroundColor: "#bfa"
}
</script>
<template>
<!-- 方式1:直接设置 -->
<div class="cr">This text is red.</div>
<!-- 方式2: 通过class绑定 -->
<div :class="arr1"> 这是class绑定1
</div>
<div :class="arr2"> 这是class绑定2
</div>
<!-- 方式3: 通过style绑定 -->
<div :style="style"> 这是style绑定
</div>
</template>
<style scoped>
.cr {
color: red;
}
.bf {
background-color: #bfa;
}
</style>
模板语法★
Vue 3的模板语法是为了将渲染的DOM与Vue实例的数据属性保持同步。它包括了一系列的指令和特殊的语法,用于模板的渲染和处理。使用这些语法和指令,可以创建动态的、响应式的用户界面。
以下是一些基本的Vue模板语法:
插值
{{ }}
:双大括号用于文本插值,可以将数据绑定到模板的文本内容中。例如:<span>{{ message }}</span>
,其中message
是Vue实例中的一个属性。注意,只能写表达式,不能使用语句。- 除了组件中的变量,vue还为我们提供了一系列全局变量可以访问,如:Data、Math、RegExp等。
- 我们也可以在main.js中声明全局变量,如
app.config.globalProperties.变量="值"
,这样就可以在任一组件中访问
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue 3!'
};
}
};
</script>
v-text
v-text
用于更新元素的textContent
,类似于{{ }}
插值,但是v-text
会替换整个元素的内容。
<template>
<p v-text="textContent"></p>
</template>
<script>
export default {
data() {
return {
textContent: 'This is some text.'
};
}
};
</script>
v-html*
v-html
用于更新元素的innerHTML
,它可以将HTML字符串插入到DOM中,但是需要注意避免XSS攻击,尽量别用。
<template>
<div v-html="htmlContent"></div>
</template>
<script>
export default {
data() {
return {
htmlContent: '<strong>This is bold text.</strong>'
};
}
};
</script>
v-bind
v-bind
用于绑定属性值,可以简写为:
。例如:<a v-bind:href="url">Link</a>
或<a :href="url">Link</a>
,其中url
是Vue实例中的一个属性。/
代表静态资源根目录public- 可以直接通过v-bind为一个标签绑定多个属性
<script setup>
const url = 'https://vuejs.org/'
const imgSrc = '/images/kun.png' //使用/public/images/kun.png无法访问,因为images前的/就是根目录public
const attrs = ref({
src: '/images/kun.png',
alt: 'kun plus',
style: 'width: 200px;'
})
</script>
<template>
<h2>超链接:<a v-bind:href="url">Visit Vue.js</a></h2>
<img :src="imgSrc" alt="kun kun" style="width: 200px;" />
<!-- 直接绑定多个属性 -->
<img :=attrs />
</template>
练习:使用v-bind切换图片
<script setup>
import {
ref
} from 'vue'
// 方式1
const imgSrc = ref('/images/kun.png')
const changeImg = () => {
imgSrc.value = '/images/kun2.png'
}
// 方式2
const attrs = ref({
src: '/images/kun.png',
alt: 'kun plus',
style: 'width: 200px;'
})
const changeImg2 = () => {
attrs.value.src = '/images/kun2.png'
}
</script>
<template>
<button type="button" @click="changeImg">切换图片1</button>
<img :src="imgSrc" alt="kun kun" style="width: 200px;" />
<br />
<button type="button" @click="changeImg2">切换图片2</button>
<img :=attrs />
</template>
v-for
v-for
用于基于源数据多次渲染一个元素或模板块。例如:<li v-for="item in items">{{ item.text }}</li>
,其中items
是一个数组,每个数组项都会渲染一个<li>
元素。
<template>
<ul>
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
]
};
}
};
</script>
v-if,v-else-if,v-else
- 这些指令用于条件渲染。
v-if
用于条件性地渲染一块内容,如果条件为false
,这块内容则不会渲染(直接移除)。v-else-if
和v-else
用于提供额外的条件渲染选项。 v-if
可以配合<template>
使用
<template>
<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Unknown Type</div>
</template>
<script>
export default {
data() {
return {
type: 'A'
};
}
};
</script>
v-show
v-show
也是用于条件渲染,与v-if
直接移除元素不同的是,v-show
的元素始终会被渲染并保留在DOM中,它只是切换元素的显示和隐藏(display)。- 切换性能比
v-if
高;但是需要对所有使用组件进行初始化,即使暂时不显示。
<template>
<div v-show="isVisible">Visible</div>
</template>
<script>
export default {
data() {
return {
isVisible: true
};
}
};
</script>
其他:
- v-on用于事件绑定,简写为
@
。例如:@click
,就是绑定了单击事件- v-model用于双向数据绑定
后面会详细讲解
事件处理
在Vue 3中,事件处理是组件与用户交互的重要部分,它允许开发者创建与用户交互的动态应用程序。事件通常与用户交互相关联,比如点击按钮、输入文本等,这些事件是浏览器原生支持的。
Vue提供了多种方式来绑定和处理事件,可以使用v-on
绑定DOM事件,使用内联事件处理器或方法事件处理器处理事件。
绑定事件
v-on
用于绑定DOM事件,可以简写为@
。例如:v-on:事件名
或@事件名
事件处理器
内联事件处理器
事件触发时,立即执行。例如:
<script setup>
import {
ref
} from "vue";
const count = ref(0);
</script>
<template>
<h2>count = {{count}}</h2>
<button @click="count++">+1</button>
</template>
方法事件处理器
事件触发时,vue自动调用事件绑定的函数。例如:
<script setup>
function handleClick() {
alert("按钮被点击...");
}
</script>
<template>
<button @click="handleClick">方法事件</button>
<button @click="handleClick()">内联事件</button>
</template>
区别
- 内联事件处理器的参数由我们自己传递,你想传递什么参数都行。
$event
就是事件对象,我们可以自己传递。 - 方法事件处理器的参数由vue自动传递,传递的参数为DOM事件对象,该对象包含事件触发时的相关信息。
<script setup>
function handleClick(...args) {
console.log(args);
}
</script>
<template>
<button @click="handleClick">方法事件</button>
<button @click="handleClick(1,2,'Hello', $event)">内联事件</button>
</template>
事件修饰符
Vue还提供了一些事件修饰符,用于在v-on
指令中绑定事件监听器时控制事件的行为。这些修饰符包括:
.stop
:阻止事件冒泡,但是不停止事件的捕获。
<script setup>
function boxHandle(box) {
console.log(box);
}
</script>
<template>
<!-- 修改为@click.stop可以解决事件冒泡 -->
<div class="box1" @click="boxHandle('box1')">
box1
<div class="box2" @click="boxHandle('box2')">
box2
<div class="box3" @click="boxHandle('box3')">
box3
</div>
</div>
</div>
</template>
<style scoped>
.box1 {
width: 400px;
height: 400px;
background-color: red;
}
.box2 {
width: 300px;
height: 300px;
background-color: yellow;
}
.box3 {
width: 200px;
height: 200px;
background-color: greenyellow;
}
</style>
.prevent
:阻止事件默认行为。
例如:
<!-- `handleClick`方法会被调用,但是按钮的默认点击行为会被阻止 -->
<button v-on:click.prevent="handleClick">Click me</button>
<!-- 简写 -->
<button @click="handleClick">Click me</button>
其他事件修饰符:
.capture
:事件侦听器会绑定到事件冒泡的起点。.self
:只当事件是从侦听器绑定的元素自身触发时才触发回调。.once
:事件侦听器在触发一次后会被移除。.passive
:告诉浏览器这个事件侦听器不会阻止事件的默认行为。
自定义事件
- vue3支持自定义事件,这与原生 DOM 事件(如点击、鼠标移动等)类似,但它是 Vue 实例上的自定义事件,可以由开发者根据需要定义和触发。
自定义事件的常见用途包括:
- 父组件监听子组件发出的事件。
- 子组件向父组件传递数据或通知状态变化。
- 兄弟组件之间的通信,通过一个共同的父组件作为中介。
- 跨组件层级的事件传递,例如祖孙组件之间的通信。
这里只提供一个简单的实例,后面详细介绍
数据绑定
单向数据绑定
假设a数据单向绑定了b数据,那么a数据的改变会影响b数据,但是b数据的改变不会影响a数据
<script setup>
import {
ref
} from 'vue';
const text = ref("");
function submitHandler() {
console.log(text);
}
</script>
<template>
<form @submit.prevent="submitHandler">
<div><input type="text" @input="(event) => (text = event.target.value)" /></div>
<div><input type="text" @input="(event) => (text = event.target.value)" /></div>
<div><button>提交</button></div>
</form>
</template>
双向数据绑定
假设a数据与b数据双向绑定,那么改变任意一个数据,另外一个数据也会随之改变
<script setup>
import {
ref
} from 'vue';
const text = ref("");
function submitHandler() {
console.log(text);//双向绑定
}
</script>
<template>
<form @submit.prevent="submitHandler">
<div><input type="text" @input="(event) => (text = event.target.value)" :value="text" /></div>
<div><input type="text" @input="(event) => (text = event.target.value)" :value="text" /></div>
<div><button>提交</button></div>
</form>
</template>
v-model
前面的双向数据是我们手动进行的,比较麻烦。vue为我们提供了v-model
进行双向数据绑定。
<script setup>
import {
ref
} from 'vue';
const text = ref("");
function submitHandler() {
console.log(text); //双向绑定
}
</script>
<template>
<form @submit.prevent="submitHandler">
<!-- 手动双向绑定 -->
<div><input type="text" @input="(event) => (text = event.target.value)" :value="text" /></div>
<!-- v-model双向绑定 -->
<div><input type="text" v-model="text" /></div>
<div><button>提交</button></div>
</form>
</template>
常用表单的双向绑定:
<script setup>
import {
ref
} from "vue"
const bool = ref("否");
const hobbise = ref([])
const gender = ref('男')
const countries = ref("")
</script>
<template>
<form>
<!-- 多选 -->
<div>是否?<input type="checkbox" v-model="bool" true-value="是" false-value="否" /></div>
<hr>
<div>
<h3>爱好</h3>
<input type="checkbox" v-model="hobbise" name="bobby" value="唱歌" />唱歌
<input type="checkbox" v-model="hobbise" name="bobby" value="跳舞" />跳舞
<input type="checkbox" v-model="hobbise" name="bobby" value="rap" />rap
<input type="checkbox" v-model="hobbise" name="bobby" value="篮球" />篮球
</div>
<hr>
<!-- 单选 -->
<div>
性别
<input v-model="gender" type="radio" name="gender" value="男" />男
<input v-model="gender" type="radio" name="gender" value="女" />女
</div>
<hr>
<!-- 下拉列表 -->
<div>
<h3>国家</h3>
<select v-model="countries">
<option disabled value="">请选择你的阵营...</option>
<option value="中国">中国</option>
<option value="米国">米国</option>
<option value="小日本">小日本</option>
</select>
</div>
<hr />
</form>
</template>
v-model的修饰符
.lazy
使用change来处理数据,只有当绑定的数据失去焦点时才改变.trim
去除前后的空格.number
将数据转换为数值,如果不是数值就不会转换。
<script setup>
import {
ref
} from 'vue';
const text = ref("");
</script>
<template>
<form>
<div>输入信息1:<input type="text" v-model="text" /><button>提交</button></div>
<div>输入信息2:<input type="text" v-model.lazy="text" /><button>提交</button></div>
<div>输入信息3:<input type="text" v-model.trim="text" /><button>提交</button></div>
<div>输入信息4:<input type="text" v-model.number="text" /><button>提交</button></div>
<div>输入信息5:<input type="text" v-model.lazy.trim="text" /><button>提交</button></div>
</form>
</template>
vue组件开发
在 Vue.js 中,组件是构建用户界面的基本单元。组件化开发是一种将应用程序拆分成多个独立、可复用的组件的思想。每个组件都有其自己的模板、样式和逻辑,它们可以独立开发、测试和部署。这种模块化的方法使得代码更加组织化、可维护和可重用。
Vue 组件的基本概念
Vue 组件是一个包含模板、逻辑和样式的结构,它可以被认为是一个自定义的 HTML 元素。组件可以有一个或多个属性(称为“props”),这些属性允许外部数据传递到组件内部。组件还可以有事件,这些事件可以在组件内部触发,并将数据传递回父组件。
我们要有组件化开发的思想,组件化开发的思想是将应用程序分解为小的、可复用的组件,每个组件都负责应用程序的一部分功能。
这种方法的优点包括:
- 代码复用: 可以创建可复用的组件,减少代码重复。
- 维护性: 组件化使得代码更易于维护和更新,因为每个组件都是独立的。
- 开发效率: 开发者可以并行开发不同的组件,提高开发效率。
- 测试性: 独立的组件更容易进行单元测试,确保功能正确性。
- 可组合性: 组件可以组合成更复杂的组件,形成丰富的组件库。
Vue 组件的创建
在 Vue 3 中,创建一个组件通常涉及以下几个步骤:
- 定义组件: 使用
Vue.component
方法或使用单文件组件(.vue 文件)来定义组件。 - 注册组件: 在父组件中注册子组件,这样就可以在父组件的模板中使用子组件。
- 使用组件: 在父组件的模板中,通过自定义元素的方式使用子组件。
组件注册
在 Vue 中,组件在使用之前必须注册。注册组件有两种方式:全局注册和局部注册。
全局注册
- 我们可以在入口文件main.js进行全局注册。全局注册的组件可以在整个应用程序中的任何组件模板内使用,不需要在每个父组件中单独导入和注册。
- 这使得全局注册的组件特别适合那些在整个应用程序中频繁使用的通用组件,如按钮、输入框等。
- 然而,全局注册的组件也意味着即使它们在某个特定页面或组件中未被使用,它们也会被包含在最终构建的代码中,这可能会导致应用程序的大小增加。因此,对于不经常使用的组件,建议使用局部注册。
如果你使用的是 Vue 3 的 Composition API,你可以这样做:
import { createApp } from 'vue';
import ExampleComponent from './ExampleComponent.vue';//注意路径
const app = createApp({});
app.component('example-component', ExampleComponent);
app.mount('#app');
局部注册
我们可以在组件的<script>
中进行局部注册,局部注册的组件只能在注册它们的组件内部使用。
组合式API写法:
<script setup>
import ExampleComponent from './ExampleComponent.vue';//注意路径
//其他逻辑
</script>
选项式API写法:
<script>
import ExampleComponent from './ExampleComponent.vue';//注意路径
export default {
components: {
ExampleComponent
},
//其他逻辑
}
</script>
组件通信
组件之间的通信是构建复杂应用程序的关键。
父子组件通信
Props
-
Props: 父组件通过 props 向子组件传递数据。
- 在子组件中,使用
defineProps()
声明需要父组件传递的值; - 在父组件中,直接在创建组件时使用v-bind向子组件传递;
- 父组件给子组件传递的值是只读的,不可修改。这个特性被称为
单向数据流
,这样设计是为了确保数据的安全性,方便快速找到数据错误的源头。 - 如果父组件给子组件传递的值是一个对象,那么可以在子组件中修改这个对象的属性。但是尽量不要通过props在父组件修改子组件的数据,要使用自定义事件修改。
- 推荐给props命名用
-
连接,例如max-length
<!-- 子组件 Hello.vue --> <script setup> const props = defineProps(["count", "stu"]); </script> <template> <h2>count = {{props.count}}</h2> <button @click="props.count++">在子组件修改值</button><!-- 无法修改 --> <hr /> <h2>全民制作人大家好,我是{{props.stu.name}},今年{{props.stu.age}}岁 </h2> <button @click="props.stu.age++">在子组件修改值的属性</button><!-- 可以修改,但是不推荐 --> </template>
<!-- App.vue --> <script setup> import Hello from './components/Hello.vue' import { reactive, ref } from 'vue'; const count = ref(0); const stu = reactive({ name: "cxk", age: 18, gender: "女", hobby: { hobby1: "唱", hobby2: "跳", hobby3: "rap", hobby4: "篮球", } }) </script> <template> <Hello :count="count" :stu="stu"></Hello> </template>
- 在子组件中,使用
自定义事件
- 自定义事件: 子组件通过自定义事件向父组件发送消息。
- 在子组件中,使用
defineEmits()
声明事件。 - 在子组件和引用它的父组件中,使用
v-on
或:
绑定事件。 - 在子组件的模板中使用
$emit()
发送事件请求,在<script>
中使用emits()
发送请求 - 自定义事件除了要调用的方法,还可以使用内联事件处理器传递其他参数
- 自定义事件可以调用本组件和引用它的父组件中的函数。
- 在子组件中,使用
示例:
<!-- 子组件 Child.vue -->
<script setup>
const emits = defineEmits(["response"])
const sendMessage = () => {
emits('response', 'Hello from child')
}
</script>
<template>
<div>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<!-- 父组件 App.vue -->
<script setup>
import Child from "./components/Child.vue"
const handleResponse = (response) => {
console.log('Received response from child:', response);
}
</script>
<template>
<div>
<Child @response="handleResponse('App.vue的参数')"></Child>
</div>
</template>
父子相互通信示例
<!-- 父组件 App.vue -->
<script setup>
import Child from "./components/Child.vue"
const parentMessage = 'Hello from parent'
const handleResponse = (response) => {
console.log('Received response from child:', response);
}
</script>
<template>
<div>
<Child :message="parentMessage" @response="handleResponse"></Child>
</div>
</template>
<!-- 子组件 -->
<script setup>
// import {} from 'vue'
const props = defineProps(["message"])
const emits = defineEmits(["response"])
const sendMessage = () => {
emits('response', 'Hello from child')
}
</script>
<template>
<div>
<p>{{ props.message }}</p>
<button @click="sendMessage">Send Message</button>
</div>
</template>
兄弟组件通信
vue.js没有提供方式让兄弟组件之间直接通信,但是可以通过共同的父组件作为中介进行间接通信,即状态提升。状态即数据,后面会详细介绍。
在上图中,要实现AAA.vue和AABA.vue共用一个count数据,要如何实现?
我们可以将count数据定义在AA.vue中,AA.vue与AAA.vue间使用props传递数据,AA.vue与AABA.vue间通过依赖注入传递数据。
由于状态提升过于麻烦,我们后面会使用状态管理进行兄弟间或其他更复杂关系的通信。
跨层级组件通信
依赖注入
Provide / Inject: 允许一个祖先组件向其所有的后代组件传递数据,无论组件层次有多深。
步骤:
- 设置依赖 provide(name,value),为子元素提供依赖
- 注入数据 const value = inject(name, default), 只能注入祖先元素的依赖
注意:
- 只能实现子孙元素读取祖先元素的值(如下图,黑色箭头表示可以提供依赖,红色不可)
- 如果有多个祖先向某个子孙提供了同一个依赖,那么注入的是最近的祖先元素的值
<!-- App.vue -->
<script setup>
import AA from "./components/AA.vue"
import AB from "./components/AB.vue"
import {
provide,
inject
} from "vue";
provide("APPMsg", "这是来自APP.vue的msg");
const msg = inject("AAAMag", "没有收到msg");
</script>
<template>
<h1>依赖注入</h1>
<h2>{{msg}}</h2>
<hr />
<AA></AA>
<hr />
<AB></AB>
</template>
<!-- AA.vue -->
<script setup>
import AAA from "./AAA.vue"
import {
provide,
inject
} from "vue";
provide("AAMsg", "这是来自AA.vue的msg");
const msg = inject("AAAMag", "没有收到msg");
</script>
<template>
<div class="type1">
<h2>组件AA的div1</h2>
<h3>{{msg}}</h3>
<AAA></AAA>
</div>
</template>
<style scoped>
.type1 {
background-color: #aaff00;
width: 300px;
height: 300px;
}
</style>
<!-- AAA.vue -->
<script setup>
import {
provide,
inject
} from "vue";
const msg = inject("APPMsg");
// const msg = inject("AAMsg");
provide("AAAMag", "这是来自AAA.vue的msg");
</script>
<template>
<div class="type3">
<h3>组件AAA的div1</h3>
<h4>{{msg}}</h4>
</div>
</template>
<style scoped>
.type3 {
background-color: #ffff00;
height: 200px;
width: 200px;
}
</style>
Event Bus
-
Event Bus 是一种简单的发布订阅模式实现,通过创建一个中央事件总线来允许任意两个组件之间通信。
-
它是一个事件中心,允许组件之间无需明确相互引用的情况下发送和接收事件。Event Bus 通常用于兄弟组件或没有直接父子关系的组件之间的通信。
-
虽然这种模式在小型项目中可能很有用,但在大型项目中可能会导致事件流难以追踪和维护。对于更复杂的状态管理需求,建议使用 Vuex 或其他状态管理库。
-
在 Vue 2 时期该方案非常流行,其特点就是 “简单” 、 “灵活” 、 “轻量级“。而在vue3,提供了一套新的组合式 API(Composition API),以及基于这个 API 的一些新特性和改进。mitt 是一个独立的库,它并不是 Vue 3 官方的一部分,但它提供了一个更现代、更简洁的方式来处理事件通信。
下面是使用
mitt
进行兄弟组件通信的示例,仿照以下步骤也可以实现没有直接父子关系的组件之间的通信。- 首先,你需要安装
mitt
库。如果你使用 npm,可以运行以下命令:
- 首先,你需要安装
npm install mitt
- 安装完成后,你可以在
main.js
或相似的入口文件中导入并使用mitt
:
import {
createApp
} from 'vue'
import App from './App.vue'
import mitt from 'mitt'
const app = createApp(App)
app.config.globalProperties.emitter = mitt() // 主要是这行
// 挂载应用
app.mount('#app')
- 创建
src/eventBus/myEventBus.js
import {
getCurrentInstance
} from 'vue'
export default function myEventBus() {
const internalInstance = getCurrentInstance()
const emitter = internalInstance.appContext.config.globalProperties.emitter
return emitter
}
- 发射事件
<!-- AA.vue -->
<template>
<div class="borther">
<h1>borther:{{ borderMsg }}</h1>
</div>
</template>
<script setup>
import {
ref
} from "vue";
//引入自定义eventBus
import myEventBus from "@/eventBus/myEventBus.js";
const borderMsg = ref("");
//创建eventBus实例
const emitter = myEventBus();
//发射事件
emitter.on("indexMsg", (value) => {
console.log("value", value);
borderMsg.value = value;
});
</script>
<style scoped>
.borther {
width: 200px;
height: 100px;
border: 1px solid red;
}
</style>
- 监听事件
<!-- AB.vue -->
<template>
<div class="index">
<h1>AB页面:{{ indexMsg }}</h1>
<button @click="getBorder">点我</button>
</div>
</template>
<script setup>
import {
ref
} from "vue";
import myEventBus from "@/eventBus/myEventBus.js";
const indexMsg = ref("AB.vue的消息");
const emitter = myEventBus();
function getBorder() {
emitter.emit("indexMsg", indexMsg);
}
</script>
<style scoped>
.index {
width: 200px;
height: 200px;
border: 1px solid skyblue;
}
</style>
- App.vue
<!-- App.vue -->
<template>
<div>
<AA />
<AB />
</div>
</template>
<script setup>
import AA from "./components/AA.vue";
import AB from "./components/AB.vue";
</script>
第三方组件库
介绍
Vue 3 的第三方组件库是指由社区成员或组织开发的、用于 Vue 3 的可重用组件集合。这些组件库提供了各种预制的 UI 组件,如按钮、输入框、模态框、表格、导航等,以帮助开发者快速构建用户界面。
以下是一些适合初学者学习且组件比较全面的 Vue 3 第三方组件库:
- Vuetify
特点:基于 Material Design,提供丰富的组件和布局,内置多种指令和工具。- Quasar Framework
特点:支持构建跨平台应用,组件丰富,包括桌面、移动端和网络应用。- Element Plus
特点:来自 Element UI 的 Vue 3 版本,适用于企业级桌面应用,组件齐全。- Naive UI
特点:轻量级,支持 TypeScript,适合想要学习 TypeScript 和现代 UI 设计的初学者。- Ant Design of Vue
特点:企业级 UI 设计语言,组件丰富,适合想要学习企业级应用开发的初学者。
基本使用(以Element Plus为例)
要在Vue3项目中使用Element Plus,你可以按照以下步骤操作:
安装Element Plus
使用npm:
npm install element-plus
引入Element Plus
在你的main.js文件中,需要引入Element Plus的样式文件,并且使用Vue的createApp()
函数创建一个应用实例,然后使用use()
方法来使用Element Plus。
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css' // 引入主题样式
const app = createApp({})
app.use(ElementPlus)
app.mount('#app')
使用Element Plus组件
现在,你可以在你的Vue组件中使用Element Plus的组件了。例如,如果你想在某个组件中使用<el-button>
组件,你可以这样做:
import { ElButton } from 'element-plus'
export default {
components: {
ElButton
}
}
然后在模板中就可以使用<el-button>
标签了:
<el-button type="primary">提交</el-button>
状态管理
介绍
- 状态(state)
- 应用当中的数据就是状态
- 视图(view)
- 视图就是用来呈现数据的,用户通过视图访问数据
- 交互(actions)
- 用户的操作
- 状态会根据用户在视图中的操作发生变化
对于更复杂的状态管理需求,状态管理是非常必要的,主要有以下几个原因:
- 代码组织:随着应用的复杂度增加,状态可能会分散在各个组件中,这样会导致代码难以组织和理解。状态管理可以帮助我们将状态集中管理,使得代码更加清晰和易于维护。
- 组件间的通信:在大型应用中,组件间的通信可能会变得相当复杂。状态管理提供了一种中心化的方式来处理这种通信,使得组件间的通信变得更加简单和直观。
- 状态的可追踪性和可预测性:状态管理允许我们更好地追踪和管理状态的变化,这对于调试和测试非常有帮助。此外,良好的状态管理还可以帮助我们预测状态的变化,从而提前做出相应的反应。
手动状态管理
在上图中,要实现AAA.vue和AABA.vue共用一个count数据,要如何实现?
状态管理的解决思路:创建一个store/count.js来存放count数据,当其他组件需要使用count,直接导入count.js
示例代码如下:
// store/count.js
import {
reactive
} from "vue"
export const countStore = reactive({
count: 0,
increment() {
this.count++;
}
})
<!-- AAA.vue -->
<script setup>
import {
countStore
} from "@/store/count" //@代表根目录
</script>
<template>
<div class="div">
<h3>AAA.vue -- count ={{countStore.count}} </h3>
<button @click="countStore.increment">按钮</button>
</div>
</template>
<style scoped>
.div {
width: 250px;
height: 100px;
background-color: #00ff00;
}
</style>
<!-- AABA.vue -->
<script setup>
import {
countStore
} from "@/store/count" //@代表根目录
</script>
<template>
<div class="div">
<h3>AABA.vue -- count = {{countStore.count}}</h3>
<button @click="countStore.increment">按钮</button>
</div>
</template>
<style scoped>
.div {
width: 250px;
height: 100px;
background-color: #00ffff;
}
</style>
pinia
介绍
- 更强的团队协作约定
- 与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试
- 模块热更新 (HMR)
- 服务端渲染支持
Pinia 就是一个实现了上述需求的状态管理库(vue插件),对 Vue 2 和 Vue 3 都可用。
- Vuex是一个集中式状态管理库,它能够为大型应用提供一个可预测的状态管理方案。而Pinia 提供了一个更简单、更现代的 API,同时保持了 Vuex 的核心功能,并且在类型支持(ts)、模块热替换和打包大小方面有所改进。如果你正在构建一个新项目,特别是使用 Vue 3 时,Pinia 可能是一个更好的选择。
- 本文只介绍功能更强大且更易学的Pinia,如果对Vuex感兴趣可以自行了解。
安装与配置
安装:
yarn add pinia
# 或者使用 npm
npm install pinia
在main.js进行以下配置:
- 引入createPinia()
- 通过
createPinia()
创建实例- 将pinia配置为vue插件
示例(main.js):
import {
createApp
} from 'vue'
import {
createPinia
} from 'pinia' //1.引入pinia
import App from './App.vue'
const pinia = createPinia() //2.创建pinia实例
const app = createApp(App)
app.use(pinia) //3.配置vue使用pinia插件
// 挂载应用
app.mount('#app')
使用步骤
选项式
创建仓库
import {
defineStore
} from "pinia"
export const useCountStore = defineStore("count", {
//数据
state: () => ({
count: 100
}),
//计算属性
getters: {
//箭头函数,不能使用this
double: (state) => state.count * 2,
//普通函数
//double(){return this.count * 2},
},
//方法
actions: {
//需要使用this访问state,不推荐使用箭头函数
increment() {
this.count++;
}
}
})
引入并使用:
<!-- AAA.vue -->
<script setup>
import {
useCountStore
} from "@/store/count"
const countStore = useCountStore()
console.log(countStore.count);
console.log(countStore.double);
</script>
<template>
<div class="div">
<h3>AAA.vue -- count = {{countStore.count}} -- double = {{countStore.double}} </h3>
<button @click="countStore.increment">按钮</button>
</div>
</template>
<style scoped>
.div {
width: 500px;
height: 100px;
background-color: #00ff00;
}
</style>
组合式
- pinia的组合式api还不完善,结构没有选项式清晰而且也没有方便到哪去,这里只做简单介绍。
- 实际开发使用哪个都行
//main.js
export const useCountStore = defineStore("count", () => {
const count = ref(50)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
return {
count,
double,
increment
}
})
对象解构
- pinia示例如果需要解构,需要使用
storeToRefs
- 选项式的计算属性不能解构,组合式的方法不能解构
//count.js
export const useCountStore = defineStore("count", () => {
//定义数据
const count = ref(50)
//定义计算属性
const double = computed(() => count.value * 2)
//定义方法
function increment() {
count.value++
}
//暴露状态
return {
count,
double,
increment
}
})
//AAA.vue脚本部分
import {
useCountStore
} from "@/store/count"//引入仓库
import {
storeToRefs
} from "pinia";
const countStore = useCountStore()
const {
count,
double
} = storeToRefs(countStore)
store的修改
直接修改
pinia解构后可以直接修改,但是不推荐使用。
//AAA.vue 脚本部分添加下面这句
count.value = 200;
间接修改
通过方法修改
<!-- AAA.vue的模板部分 -->
<template>
<div class="div">
<h3>AAA.vue -- count = {{count}} -- double = {{double}} </h3>
<button @click="countStore.increment">按钮</button><!-- 点击调用方法间接修改 -->
</div>
</template>
$patch
补丁修改
- pinia提供了
$patch
进行补丁修改,可以同时修改多个状态 $patch
会直接替换之前的同名状态
const clickHandler = () => {
countStore.$patch({
count: 200
})
//或
countStore.$patch((state) => {
state.count = 300
})
}
$reset
状态重置
将修改后的状态重置回初始状态
模板写法(只适用于选项式api)
<button @click="countStore.$reset()">重置</button>
如果使用组合式api,需要我们在main.js重写$reset
import {
createApp
} from 'vue'
import {
createPinia
} from 'pinia' //1.引入pinia
import App from './App.vue'
const pinia = createPinia() //2.创建pinia实例
const app = createApp(App)
pinia.use(({
store
}) => {
const initialState = JSON.parse(JSON.stringify(store.$state));
store.$reset = () => {//重写$rest
store.$patch(initialState);
}
})
app.use(pinia) //3.配置vue使用pinia插件
// 挂载应用
app.mount('#app')
store的监听(订阅)
- 使用
$subsribe
监听store是否变化,如果变化就会调用$subsribe
中的方法。$subsribe
只会监听定义它的组件是否修改了store,不会监听其他组件有无修改。 mutation
表示修改的信息。如果使用$patch
,mutation.payload
会记录新的状态(数据)。detached: true
表示即使组件被移除也会继续监听- 使用
$subsribe
时不要在其回调函数中修改state
//在任一使用了仓库的组件中(AAA.vue)
countStore.$subscribe(
(mutation, state) => {
//console.log(mutation.events)
console.log(mutation.payload)
console.log("state发生了变化,变化后的state:", state)
}, {
detached: true
}
)
类似的还有$onAction
,用来监听选项式api的actions或组合式api方法是否调用
countStore.$onAction(() => {
console.log("actions执行了")
})
可以传递以下参数:
- name 调用的action的名称
- store store的实例
- args action接收到的参数
- after() 设置一个回调函数,该函数会在action成功调用后触发
- onError() 设置一个回调函数,该函数会在action失败调用后触发
countStore.$onAction((name,store,agrs,after,onError) => {
console.log("actions name:",name)
console.log("actions store:",store)
console.log("actions agrs:",agrs)
after(() =>{
console.log("actions 执行成功")
})
})
路由管理Vue Router
路由的基本概念
Vue 3中的路由是指通过前端路由器(Vue Router)来管理应用程序的不同页面之间的导航和状态。它允许你在页面之间进行切换,而无需进行完整的页面刷新,从而实现更流畅和快速的用户体验。
使用Vue Router,你可以轻松地构建具有多个页面和复杂导航逻辑的Vue应用程序。
下面是Vue 3中路由的一些基本概念:
路由器(Router):Vue Router是Vue官方提供的路由管理器。它允许你在Vue应用程序中定义和管理路由。你可以使用Vue Router来定义路由规则,注册路由组件,并在应用程序中进行导航。
路由(Route):路由是指应用程序中的一个特定页面。每个路由对应一个URL路径,并且关联一个组件。通过路由,你可以在不同的URL路径之间进行导航。
路由组件(Route Component):路由组件是Vue中与特定路由关联的组件。每个路由都有一个对应的路由组件,用于渲染该路由对应的页面内容。
路由表(Route Table):路由表是指在Vue Router中定义的路由规则。它包含了每个路由的路径、对应的组件以及其他相关配置,如路由的名称、参数等。
动态路由(Dynamic Route):动态路由是指具有动态片段的路由。例如,你可以定义一个动态路由来匹配用户的ID或其他参数,以便根据不同的参数显示不同的内容。
嵌套路由(Nested Route):嵌套路由是指一个路由中包含另一个路由的情况。通过嵌套路由,你可以构建出多层级的页面结构,实现更复杂的导航和页面组织。
路由导航(Route Navigation):路由导航指的是在应用程序中切换路由的过程。你可以通过编程方式或在模板中使用
<router-link>
组件进行路由导航。路由参数和查询参数(Route Parameters and Query Parameters):路由参数用于传递动态的路径参数,如用户ID等。查询参数用于传递额外的信息,以键值对的形式附加在URL中。
路由的安装和设置
在 Vue 3 中,使用 Vue Router 来处理客户端路由。Vue Router 是 Vue 官方推荐的路由库,它允许你为不同的路径设置对应的组件,从而实现单页面应用(SPA)的页面切换和视图更新。
安装 Vue Router
首先,你需要安装 Vue Router。如果你使用 npm,可以运行以下命令:
npm install vue-router@4
或者,如果你使用 yarn,可以运行:
yarn add vue-router@4
注意,这里我们指定了版本 4,因为 Vue 3 对应的是 Vue Router 的第 4 版本。
设置 Vue Router
创建路由实例
创建一个 router
文件夹,并在其中创建一个 index.js
文件来设置路由。
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
这里我们使用了 createWebHistory
创建一个 history 模式的路由。如果你需要 hash 模式,可以使用 createWebHashHistory
。
在主文件中引入路由
在你的主 Vue 文件(通常是 main.js
或 main.ts
)中,你需要导入并使用这个路由实例。
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');
通过调用 app.use(router)
,你将路由插件安装到了你的 Vue 应用中。
在 Vue 组件中使用路由
现在你可以在你的 Vue 组件中使用 router-view
和 router-link
组件来渲染路由和创建导航链接。
<!-- App.vue -->
<template>
<div id="app">
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-view></router-view>
</div>
</template>
<router-view>
是一个容器组件,它会根据当前路由动态渲染匹配的组件。<router-link>
是一个组件,用于创建导航链接,它接受 to
属性来指定链接的目标路径。
路由导航
你可以使用 this.$router
访问路由实例,并使用它来进行编程式导航。
// 在组件内部
this.$router.push('/about');
路由守卫
Vue Router 提供了全局守卫、路由独享守卫和组件内守卫,允许你控制路由的访问权限和导航行为。
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 执行一些逻辑判断
next(); // 或者 next(false) 来中断导航
});
懒加载路由组件
为了提高应用的性能,你可以使用动态导入来懒加载路由组件。
const routes = [
{
path: '/',
component: () => import('../views/Home.vue')
},
{
path: '/about',
component: () => import('../views/About.vue')
},
];
以上就是在 Vue 3 中安装和设置 Vue Router 的基本步骤。Vue Router 提供了丰富的功能,如嵌套路由、路由参数、导航守卫等,你可以根据项目需求进行更深入的了解和使用。
嵌套路由和路由参数
在Vue 3中,嵌套路由和路由参数是强大且常用的功能,可以帮助你构建具有多层级页面结构和动态路径参数的应用程序。下面详细介绍一下Vue 3中的嵌套路由和路由参数的概念和用法:
- 嵌套路由(Nested Route):
嵌套路由允许你在一个路由中嵌套其他路由,从而构建多层级的页面结构。这对于构建具有层级关系的应用程序非常有用。
在Vue 3中,你可以在路由配置中的children
属性中定义嵌套路由。例如:
const routes = [
{
path: '/parent',
component: ParentComponent,
children: [
{
path: 'child',
component: ChildComponent
}
]
}
];
在上面的例子中,ParentComponent
是父级路由的组件,ChildComponent
是嵌套在父级路由中的子级路由的组件。当访问/parent/child
路径时,Vue Router会渲染ParentComponent
和ChildComponent
。
- 路由参数(Route Parameters):
路由参数用于动态地传递路径参数,例如用户ID、商品ID等。在Vue 3中,你可以通过在路由配置中使用:
来定义路由参数。例如:
const routes = [
{
path: '/user/:id',
component: UserComponent
}
];
在上面的例子中,:id
是一个路由参数,它会匹配用户ID。当访问/user/123
路径时,Vue Router会将123
作为路由参数传递给UserComponent
组件。
在路由参数中还可以定义多个参数,例如:
const routes = [
{
path: '/user/:id/post/:postId',
component: UserPostComponent
}
];
在上面的例子中,:id
和:postId
都是路由参数,它们会匹配用户ID和帖子ID。当访问/user/123/post/456
路径时,Vue Router会将123
和456
作为路由参数传递给UserPostComponent
组件。
在组件中,你可以通过$route.params
来访问路由参数。例如,在上面的例子中,可以通过this.$route.params.id
来获取用户ID。
除了路由参数,还可以使用查询参数(Query Parameters)来传递额外的信息。查询参数是以键值对的形式出现在URL中,例如/user?id=123
。在组件中,你可以通过$route.query
来访问查询参数。
这是关于Vue 3中嵌套路由和路由参数的详细介绍。通过使用嵌套路由和路由参数,你可以构建出更复杂和灵活的Vue应用程序。
示例-极简音乐
-
创建工程并安装Vue Router
-
删除HelloWorld.vue和其在App.vue中的相关代码,清除其他无用代码
-
创建几个简单的组件(/components):Discover.vue,MyMusic.vue,Follow.vue
添加以下基本代码,以Discover.vue为例,其他类似
<template>
<div>
<h1 class="title">发现音乐</h1>
</div>
</template>
<script>
</script>
<style>
.title{
color: red;
}
</style>
注意,如果你每个组件的class一样,会导致样式冲突。这是因为它们是全局样式,当组件被加载到同一个页面时,最后一个加载的组件的样式会覆盖之前的样式。
要避免这个问题,你需要在每个组件的style标签中添加scoped
属性,例如
<style scoped>
.title{
color: red;
}
</style>
当然,你还可以使用如 SCSS 或 LESS 这样的 CSS 预处理器,不过这里只采用比较简单的方式。
你也可以利用这一特性,直接在根组件中定义全局样式,然后在其他子组件中使用全局样式。
- 根组件(App.vue)声明路由导航
<template>
<div id="app">
<nav class="nav-link">
<router-link to="/discover">发现音乐</router-link>
<router-link to="/myMusic">我的音乐</router-link>
<router-link to="/follow">关注</router-link>
</nav>
<!-- 声明路由占位符 -->
<router-view></router-view>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.nav-link a{
/ * 不建议直接使用标签,推荐使用指定类名设置样式。 */
/* 直接使用.nav-link router-link无法指定router-link的样式,router-link 组件在渲染时会转换为一个 <a> 标签 */
display: inline-block;
width: 100px;
padding: 0 5px;
}
</style>
- 创建src/router/index.js,定义对应关系
import { createRouter, createWebHistory } from 'vue-router'
import Discover from '@/components/Discover.vue'
import Follow from '@/components/Follow.vue'
import MyMusic from '/src/components/MyMusic.vue'//也可以不使用别名
// 创建路由实例
const router = createRouter({
// 使用 createWebHistory() 创建历史记录
history: createWebHistory(),
routes: [
{path: '/', redirect:"/discover"},//路由重定向
{ path: '/discover', component: Discover},
{ path: '/follow', component: Follow},
{ path: '/myMusic', component: MyMusic},
]
})
// 导出路由实例
export default router
注意:这里使用了 @ 符号作为 src 目录的别名,你需要在你的项目根目录下创建或编辑 vite.config.js (或vue.config.js)文件,添加以下内容来配置 @ 别名:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve:{
alias: {
//指定别名
'@': '/src',
}
}
})
- 在main.js挂载路由
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
// 可以通过 app.config 设置一些全局配置
app.config.productionTip = false
// 使用路由
app.use(router)
// 挂载应用程序
app.mount('#app')
7.路由嵌套:修改Discover.vue
,修改后的代码如下
<template>
<div>
<h1 class="title">发现音乐</h1>
<!-- 子路由链接 -->
<nav class="nav-link">
<router-link to="/discover/toplist">推荐</router-link>
<router-link to="/discover/playlist">歌单</router-link>
</nav>
<!-- 声明路由占位符 -->
<router-view></router-view>
</div>
</template>
<script>
</script>
<style scoped>
.title{
color: red;
}
.nav-link{
background-color: #bfa;
}
</style>
- 创建组件
TopList.vue
,PlayList.vue
<template>
<h3>推荐</h3>
</template>
<!-- PlayList.vue类似 -->
- 修改
src/router/index.js
,添加子路由对应关系
import {
createRouter,
createWebHistory
} from 'vue-router'
import Discover from '@/components/Discover.vue'
import Follow from '@/components/Follow.vue'
import MyMusic from '@/components/MyMusic.vue'
import PlayList from '@/components/PlayList.vue'
import TopList from '@/components/TopList.vue'
// 创建路由实例
const router = createRouter({
// 使用 createWebHistory() 创建历史记录
history: createWebHistory(),
routes: [{
path: '/',
redirect: "/discover"
}, //路由重定向
{
path: '/discover',
component: Discover,
children: [ // 使用数组定义嵌套路由
{
path: "toplist",
component: TopList
},
{
path: "playlist",
component: PlayList
},
]
},
{
path: '/follow',
component: Follow
},
{
path: '/myMusic',
component: MyMusic
},
]
})
// 导出路由实例
export default router
- 老实说,配置 @ 别名为/src还是保守了,我们可以取一个别名为
@cpns
,其路径为/src/components
//vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve:{
alias: {
'@': '/src',
'@cpns': '/src/components',
}
}
})
- 修改MyMusic.vue,添加
Music.vue
<template>
<div>
<h1 class="title">我的音乐</h1>
<ul class="music-list">
<li><router-link to="/music/1">Music1</router-link></li>
<li><router-link to="/music/2">Music2</router-link></li>
<li><router-link to="/music/3">Music3</router-link></li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
</script>
<style scoped>
.title {
color: orange;
}
.music-list li {
color: blue;
list-style: none;
margin-top: 10px;
}
</style>
<!-- Music.vue -->
<template>
<h3>Music</h3>
</template>
<script>
</script>
<style>
</style>
- 修改
src/router/index.js
,添加子路由对应关系(只修改myMusic路由)
{
path: '/myMusic',
component: MyMusic,
children: [{
path: ':id',
component: Music,
props: true//方便传递props自定义属性
}]
},
- 为了看出区别,我们修改
Music.vue
<template>
<h3>Music{{id}}</h3>
</template>
<script>
export default {
props: ["id"],
}
</script>
<style>
</style>
http://localhost:3000/myMusic/1
效果:
HTTP通信与后端交互
与后端进行数据交互
axios
在Vue 3中,你可以使用axios
库与Spring Boot后端进行数据交互。
axios
是一个基于promise网络请求库,作用域node.js和浏览器中
axios
在浏览器端使用XMLHttpRequests发送网络请求,并能自动完成JSON数据的转换
一般步骤
下面是使用axios
的一般步骤:
- 安装
axios
库:在终端或命令行中运行以下命令来安装axios
库:
npm install axios
- 创建一个API服务:可以在Vue项目中创建一个单独的文件,例如
api.js
,用于封装与后端的API交互。
在api.js
中,你可以导入axios
库并创建一个实例,然后定义与后端API的交互方法。例如:
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'http://localhost:8080' // 根据你的后端配置设置正确的 baseURL
});
export default {
// GET 请求示例
fetchItems() {
return apiClient.get('/items');
},
// POST 请求示例
createItem(item) {
return apiClient.post('/items', item);
},
// 其他请求示例(PUT、DELETE等)
// ...
};
- 在Vue组件中使用API服务:在需要与后端进行数据交互的Vue组件中,可以导入
api.js
并使用其中定义的方法。例如:
import api from '@/api.js';
export default {
// ...
methods: {
fetchItems() {
api.fetchItems()
.then(response => {
// 处理响应数据
console.log(response.data);
})
.catch(error => {
// 处理错误
console.error(error);
});
},
createItem(item) {
api.createItem(item)
.then(response => {
// 处理响应数据
console.log(response.data);
})
.catch(error => {
// 处理错误
console.error(error);
});
},
// ...
}
};
这是一个简单的示例,展示了如何使用axios
与Spring Boot后端进行数据交互。你可以根据具体的项目需求和后端API设计,进一步扩展和调整这些代码。
请求响应
发送 GET 请求
import axios from 'axios';
axios.get('https://api.example.com/data')
.then(response => {
// 处理成功情况
console.log(response.data);
})
.catch(error => {
// 处理错误情况
console.error(error);
});
发送 POST 请求
import axios from 'axios';
const data = {
name: '张三',
age: 30
};
axios.post('https://api.example.com/data', data)
.then(response => {
// 处理成功情况
console.log(response.data);
})
.catch(error => {
// 处理错误情况
console.error(error);
});
使用 async/await
你也可以使用 ES2017 引入的 async/await
语法来处理 Axios 请求。
import axios from 'axios';
async function fetchData() {
try {
const response = await axios.get('https://api.example.com/data');
console.log(response.data);
} catch (error) {
console.error(error);
}
}
fetchData();
Axios 实例
你还可以创建一个 Axios 实例,以便在多个请求之间共享配置。
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://api.example.com'
});
instance.get('/data')
.then(response => {
// 处理成功情况
console.log(response.data);
})
.catch(error => {
// 处理错误情况
console.error(error);
});
请求和响应拦截器
Axios 提供了拦截器功能,允许你在请求发送之前或响应到达之前对其进行处理。
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
错误处理
Axios 请求失败时,会返回一个错误对象。这个对象包含一个 response
属性,该属性包含了服务器返回的信息。
axios.get('https://api.example.com/data')
.catch(error => {
if (error.response) {
// 请求已发出,服务器响应的状态码不在 2xx 范围内
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 请求已发出,但没有收到响应
console.log(error.request);
} else {
// 发生了设置请求时的某些问题
console.log('Error', error.message);
}
console.log(error.config);
});
全局配置Axios
在实际项目开发中,几乎每个组件中都会用到axios发起数据请求。此时会遇到如下两个问题.
- 每个组件中都需要导入axios
- 每次发请求都需要填写完整的请求路径
可以通过全局配置的方式(在main.js中)解决上述问题:
跨域(后端解决)*
同源策略
- 为了保证浏览器的安全,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,称为同源策略,同源策略是浏览器安全的基石
- 同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能
- 所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
- 当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域,此时无法读取非同源网页的Cookie,无法向非同源地址发送AJAX请求
例如:当你的前端工程运行在http://localhost:8080
,而你的后端工程运行在http://localhost:8088
,那么就会报错。因为你的前后端工程的端口不一样(即跨域了),前端无法向后端发送AJAX请求。
CORS
介绍
CORS(Cross-OriginResourceSharing)是由W3C制定的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求。
CORS可以在不破坏即有规则的情况下,通过后端服务器实现CORS接口,从而实现跨域通信。
CORS将请求分为两类:简单请求和非简单请求,分别对跨域通信提供了支持。
如果你感兴趣你可以详细了解CORS,但事实上,后端springboot提供的更简洁的方法
方法
- 后端需要添加一个springboot的配置类就可以进行全局配置
- 后端也可以在需要跨域的controller之下添加
@CrossOrigin
注解解决
MockJS
Mock.js介绍
什么是Mock.js?
Mock.js 是一款前端开发中拦截Ajax请求再生成随机数据响应的工具,可以用 来模拟服务器响应
Mock.js的特性
- 使用mockjs模拟后端接口,可随机生成所需数据,模拟对数据的增删改查
- 数据类型丰富,支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。
- 拦截Ajax请求不需要修改既有代码就可以拦截,返回模拟的响应数据。
官网:http://mockjs.com
Mock.js的使用
安装
npm install axios mockjs --save-dev # 安装axios和mockjs
使用Mock.js
- 在src目录下创建mock目录,定义mock主文件index.js,并在该文件中存放我们的mock数据
- 在main.js引入mock文件,方便在其他文件中使用(无需其他操作)
import "@/mock/index";
- 编写mock/index.js,配置要拦截的请求与返回的数据
import Mock from 'mockjs';
// 拦截Ajax请求,/api/users是拦截的请求地址
Mock.mock('/api/users', 'get', {
// 返回随机数据
'data|1-10': [{
'id|+1': 1,
'name': '@cname', // 随机生成中文名字
'email': '@email', // 随机生成邮箱
'date': '@date("yyyy-MM-dd")' // 随机生成日期
}]
});
// 如果需要,还可以继续添加其他拦截规则
// Mock.mock('/api/orders', 'get', {...});
- 在相关组件接收响应回来的数据(这里以App.vue为例)
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
const users = ref([]);
const fetchUsers = async () => {
try {
const response = await axios.get('/api/users');
users.value = response.data;
} catch (error) {
console.error('There was an error!', error);
}
};
onMounted(() => {
fetchUsers();
});
</script>
<template>
<div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users.data" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>{{ user.date }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
/* 添加一些样式来美化表格 */
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ccc;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
font-weight: bold;
}
</style>
效果如下:
Mock.Random
- Mock.Random 是一个工具类,用于生成各种随机数据。
- Mock.Random 的方法在数据模板中称为『占位符』,书写格式为 @占位符(参数) 。
const Mock = require("mockjs")
const Random = Mock.Random
export const userData = Mock.mock("/data/list", "post", {
// 属性 list 的值是一个数组,随机生成 1 到 10 个元素
"list|1-10": [
{
// 生成随机字符串 长度为 5
"string": Random.string(5), // "jPXEu"
"string2": '@string(5)', // "jPXEu"
// 生成随机邮箱地址 可以指定域名,例如 163.com
"email": Random.email('163.com'), // "l.fvilfpz@163.com"
"email2": '@email()', // "l.fvilfpz@163.com"
// 返回一个随机的布尔值。
"boolean": Random.boolean(), // true
"boolean2": '@boolean()', // true
// 生成 60-100 随机整数
"point": Random.integer(60, 100), // 69
"point2": '@integer(60, 100)', // 98
// // 生成一个浮点数,整数部分大于等于 1、小于等于 100,小数部分保留 3 到 5 位。
"floatNumber": Random.float(1, 100, 3, 5), // 60.695
"floatNumber2": '@float(1, 100, 3, 5)', // 19.29368
// 随机日期
"date": Random.datetime('yyyy-MM-dd'), // "2017-05-01"
"date2": "@datetime()", // "1973-06-12 13:05:18"
// 随机时间
"time": Random.time(), // "21:33:01"
"time2": "@time()", // "21:33:01"
// 当前日期
"now": Random.now('year'), // "2023-01-01 00:00:00"
"now2": "@now('year')", // "2023-01-01 00:00:00"
// 随机生成图片 Random.image( size, background, foreground, format, text )
"img": Random.image('200x100', '#16d46b', '#fff', 'png', 'Hello'), // "http://dummyimage.com/200x100/16d46b/fff.png&text=Hello"
// 随机生成颜色,格式为 '#RRGGBB'。
"color": Random.color(), // "#94f279"
"color2": '@color()', // "#94f279"
// 随机生成颜色,格式为 'rgb(r, g, b, a)'。
"rgbaColor": Random.rgba(), // "rgba(242, 121, 183, 0.22)"
// 随机生成一段文本 文本中句子的个数为 2 到 5。默认值为 3 到 7
"paragraph": Random.paragraph(2, 5), // "Ymkp nvyryy vieq hlqdb pplbbikbd mtqiq uue jdufhkxy wpybjqi djico jxqkwvw kbmsscpfw owtgsqwn."
"paragraph2": '@paragraph(2, 5)', // "Ymkp nvyryy vieq hlqdb pplbbikbd mtqiq uue jdufhkxy wpybjqi djico jxqkwvw kbmsscpfw owtgsqwn."
// 随机生成一段中文文本 参数同 Random.paragraph( min?, max? )
"cparagraph": Random.cparagraph(), // "重工边政应信江半实金改北反调程五八。张资圆向规成新家天交对传许。军较军七养多认维市般况验式华行证。"
"cparagraph2": '@cparagraph(2, 5)', // "重工边政应信江半实金改北反调程五八。张资圆向规成新家天交对传许。军较军七养多认维市般况验式华行证。"
// 随机生成一个句子,第一个单词的首字母大写。 句子中单词的个数为 2 到 5 。默认值为 12 到 18
"sentence": Random.sentence(2, 5), // "Yyfvs genrdeiyf."
"sentence2": '@sentence(2, 5)', // "Yyfvs genrdeiyf."
// 随机生成一段中文文本,参数同 Random.sentence( min?, max? )
"csentence": Random.csentence(2, 5), // "积现。"
"csentence2": '@csentence(2, 5)', // "积现。"
// 随机生成一个单词,单词中字符的个数为 2 到 5 个。默认值为 3 到 10
"word": Random.word(2, 5), // "nlgcl"
"word2": '@word(2, 5)', // "nlgcl"
// 随机生成一个汉字,汉字中字符串的长度为 2 到 5 个。默认值为 1
"cword": Random.cword(2, 5), // "系即感"
"cword2": '@cword(2, 5)', // "系即感"
// 随机生成一句标题,其中每个单词的首字母大写。单词中字符的个数为 2 到 5。默认值为 3 到 7
"title": Random.title(2, 5), // "Vmpx Rizds Smguoqki"
"title2": '@title(2, 5)', // "Vmpx Rizds Smguoqki"
// 随机生成一句中文标题,参数同 Random.title( min?, max? )
"ctitle": Random.ctitle(2, 5), // "其感期"
"ctitle2": '@ctitle(2, 5)', // "其感期"
// 随机生成一个常见的英文名
"firstName": Random.first(), // "Michelle"
"firstName2": '@first()', // "Jose"
// 随机生成一个常见的英文姓。
"lastName": Random.last(), // "Taylor"
"lastName2": '@last()', // "Clark"
// 随机生成一个常见的英文姓名。括号里的布尔值,指示是否生成中间名(可选)。
"name": Random.name(true), // "Donald Eric Jackson"
"name2": '@name(true)', // "Donald Eric Jackson"
// 随机生成一个常见的中文姓
"cfirstName": Random.cfirst(), // "任"
"cfirstName2": '@cfirst()', // "郭"
// 随机生成一个常见的中文名。
"clastName": Random.clast(), // "芳"
"clastName2": '@clast()', // "芳"
// 随机生成一个常见的中文姓名。
"cname": Random.cname(), // "程强"
"cname2": '@cname()', // "程强"
// 随机生成一个URL。可以指定url协议,域名和端口号。例如'http' nuysoft.com。
'url': Random.url('http', 'nuysoft.com'), // "http://nuysoft.com/ysq"
'url2': '@url()', // "http://nuysoft.com/ysq"
// 随机生成一个 IP 地址
'IP': Random.ip(), // "112.127.151.37"
'IP2': '@ip()', // "233.144.17.219"
// 随机生成一个(中国)大区。
"region": Random.region(), // "华北"
"region2": '@region()', // "华北"
// 随机生成一个(中国)省(或直辖市、自治区、特别行政区)。
"province": Random.province(), // "澳门特别行政区"
"province2": '@province()', // "澳门特别行政区"
// 随机生成一个(中国)市。括号里的布尔值,指是否生成所属的省(可选)
"city": Random.city(true), // "广东省 肇庆市"
"city2": '@city()', // "广东省 肇庆市"
// 随机生成一个(中国)县。括号里的布尔值,指是否生成所属的省、市(可选)
"county": Random.county(true), // "江苏省 常州市 其它区"
"county2": '@county()', // "江苏省 常州市 其它区"
// 随机生成一个邮政编码(六位数字)。
"zip": Random.zip(), // "806124"
"zip2": '@zip()', // "806124"
// 把字符串的第一个字母转换为大写。
"capitalize": Random.capitalize('hello'), // "Hello"
"capitalize2": '@capitalize("hello")', // "Hello"
// 把字符串转换为大写。
"upper": Random.upper('hello'), // "HELLO"
"upper2": '@upper("hello")', // "HELLO"
// 把字符串转换为小写。
"lower": Random.lower('HELLO'), // "hello"
"lower2": '@lower("HELLO")', // "hello"
// 从数组中随机选取一个元素并返回。
"pick": Random.pick(['a', 'e', 'i', 'o', 'u']), // "e"
"pick2": '@pick(["a", "e", "i", "o", "u"])', // "e"
// 打乱数组中元素的顺序,并返回。
"shuffle": Random.shuffle(['a', 'e', 'i', 'o', 'u']), // ['o', 'a', 'i', 'e', 'u']
"shuffle2": '@shuffle(["a", "e", "i", "o", "u"])', // ['o', 'a', 'i', 'e', 'u']
// 随机生成一个 18 位身份证。
"id": Random.id(), // 112.127.151.37
"id2": '@id()' // 97.46.129.222
},
],
code: 200,
message: 'ok',
});
更多细节请参考官网。