Vue进阶¶
1. 组件化开发¶
1.1 脚本化组件[component]¶
组件(Component)是自定义封装代码的功能。在前端开发过程中,经常出现多个网页的功能是重复的,而且很多不同的页面之间,也存在同样的功能。
而在网页中实现一个功能,需要使用html定义功能的内容结构,使用css声明功能的外观样式,还要使用js来定义功能的特效,因此就产生了把一个功能相关的[HTML、css和javascript]代码封装在一起组成一个整体的代码块封装模式,我们称之为“组件”。
所以,组件就是一个html网页中的功能,一般就是一个标签,标签中有自己的html内容结构,css样式和js特效。
这样,前端人员就可以在组件化开发时,只需要书写一次代码,随处引入即可使用。
vue的组件有两种:脚本化组件 和 单文件组件。
脚本化组件的组件名:单词小写加横杠。
1.1.1 脚本化组件¶
1.1.1.2 全局注册¶
assets/components.js,代码:
// 封装一个全局注册的组件
const header_nav = {
data(){ // 组件内部的数据
return {
nav_list: ["首页","开放平台", "企业团购", "资质证照", "协议规则", "下载app"],
num: 0,
}
},
methods:{ // 组件内部的操作方法
show(nav){
console.log(nav);
}
},
// 组件的HTML代码
template:`
<div class="header">
<ul>
<li v-for="nav in nav_list"><a href="" @click.prevent="show(nav)">{{nav}}</a></li>
</ul>
<button @click="num++">{{num}}</button>
</div>
`,
}
html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>todolist</title>
<script src="assets/vue.global.3.2.20.js"></script>
<script src="assets/components.js"></script>
</head>
<body>
<div id="app">
<header-nav></header-nav>
<header-nav></header-nav>
<header-nav></header-nav>
</div>
<script>
var vm = Vue.createApp({
data(){
return {
}
},
}).component("header-nav", header_nav).mount("#app")
</script>
</body>
</html>
1.1.1.3 局部注册¶
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>todolist</title>
<script src="assets/vue.global.3.2.20.js"></script>
<script src="assets/components.js"></script>
</head>
<body>
<div id="app">
<header-nav></header-nav>
<header-nav></header-nav>
</div>
<script>
var vm = Vue.createApp({
data(){
return {
}
},
// 局部注册
components: {
// "header-nav": headerNav,
headerNav, // 等价于上面一句代码,vue会自动转换驼峰式为匈牙利写法
}
}).mount("#app")
</script>
</body>
</html>
组件与组件之间,代码隔离,数据隔离。同一个组件被使用多次,实际上是对组件的对象进行了多次实例化,所以本质上在内存就不是同一个对象。因此才会出现数据隔离的情况。
2. 项目构建工具¶
在前端开发中, 前端开发人员为了方便快速生成和管理项目,一般都会使用一些项目构建工具来辅助开发,而我们接下来要学习的单文件组件则必须依赖于项目构建工具所生成的运行环境才能完成这块的学习。目前来说,vue开发中一般常用的项目构建工具无非2个:vue-cli和vite2这2款最多人使用。
2.0 Vue自动化工具(Vue-cli)¶
vue2.0官方推荐使用vue-cli,vue3.0官方务必使用vite2.0。
一般情况下vue的单文件组件使用中,我们运行都会在vue-CLI中进行开发,它可以帮我们把单文件组件编译成普通的js代码。所以我们需要在电脑先安装搭建vue-CLI工具。
官网:https://cli.vuejs.org/zh/
Vue-CLI 由nodejs编写的,所以我们推荐安装Node.js 10.0以上的开发环境。你可以使用 nvm 或 nvm-windows在同一台电脑中管理多个 Node 解释器版本。
mac OS系统下nvm工具的下载和安装:https://www.jianshu.com/p/622ad36ee020
windows安装记录:
打开: https://github.com/coreybutler/nvm-windows/releases
安装完成以后,先查看环境变量是否设置好了.
常用的nvm命令
| 命令 | 描述 |
|---|---|
nvm ls-remote |
列出远程服务器上所有的可用版本,当前命令需要使用网络 |
nvm list |
列出目前在本地的nvm里面安装的所有node版本 |
nvm install <node版本号> |
安装指定版本的node.js |
nvm uninstall <node版本号> |
卸载指定版本的node.js |
| nvm use |
临时切换当前使用的node.js版本 |
nvm alias default <node版本号> |
切换当前系统默认使用的node.js版本,设置指定版本的node.js解析器为默认版本 |
如果使用nvm工具,则直接可以不用自己手动下载nodejs,如果使用nvm下载安装nodejs的npm比较慢的时候,可以修改nvm的配置文件(在安装根目录下)。
在window系统下如果安装node的时候比较慢,则可以修改一下配置文件:
# 配置文件名:settings.txt
root: C:\tool\nvm [这里的目录地址是安装nvm时自己设置的地址,要根据实际修改]
path: C:\tool\nodejs
arch: 64
proxy: none
node_mirror: http://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/
2.1 node.js¶
Node.js发布于2009年5月,由谷歌工程师Ryan Dahl(瑞安·达尔)开发的JavaScript运行环境,一个让 JavaScript代码运行在服务端的开发平台,它让javascript变成了服务端语言,所以nodejs开发者编写的代码本质上就是javascript代码。
后端语言和前端语言的区别:
- 运行环境:后端语言一般运行在服务器端,前端语言运行在客户端的浏览器上。
- 功能:后端语言可以操作文件/网络/进程/线程等,可以读写数据库,前端语言不能主动操作本地文件,不能读写数据库。
node.js的版本有两大分支:
奇数版本: 每年10月份发布,版本号为奇数,例如:17.0.0,叫当前版本(Current Version),维护期是3个月。
偶数版本: 次年04月份发布,版本号为偶数,例如:16.13.0, 叫长线支持版本(Long-Time Support Version),维护期是3年。
我们一般安装LTS(长线支持版本 Long-Time Support):
下载地址:https://nodejs.org/en/download/【上面已经安装了nvm,那么这里不用手动安装了】
文档:https://nodejs.org/zh-cn/about/releases/
Node.js如果安装成功,可以查看Node.js的版本,在终端输入如下命令:
2.2 npm包管理器¶
在安装node.js完成后,在node.js中会同时帮我们安装一个包管理器npm。我们可以借助npm命令来安装node.js的包。这个工具相当于python的pip包管理器。
| 命令 | 描述 |
|---|---|
npm install -g <包名> |
安装node包模块 -g表示全局安装,如果没有-g,则表示在当前项目安装 npm install <包名> 可以简写成"npm i <包名>" |
npm list |
查看当前项目目录下已安装的node包模块 |
| npm view <包名> engines | 查看node包模块所依赖的Node的版本 |
| npm outdated | 检查node包模块是否已经过时,命令会列出所有已过时的node包模块 |
npm update <包名> |
更新node包模块 |
npm uninstall <包名> |
卸载node包模块 |
| npm <包名> -h | 查看指定node包模块的相关命令或帮助文档 |
| npm run <子选项> | 启动项目 |
注意:
npm虽然是nodejs官方提供的包管理工具,但事实上并不好用,所以有第三方开发者提供了更好用的yarn包管理器。
2.3 yarn包管理器¶
ubuntu20.04下安装yarn
# 注意:这里我们已经把我的node.js的版本换成了v16.16.0版本了。
# 大家如果也想使用这个使用版本,终端下执行以下命令
nvm install v16.16.0
nvm alias default v16.16.0
nvm use v16.16.0
# 安装yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install -y yarn
# sudo apt remove yarn # 卸载yarn
# sudo apt autoremove # 卸载yarn相关其他依赖
windows下安装yarn
2.3.1 yarn基本使用命令¶
| 命令 | 描述 |
|---|---|
| yarn global add <包名> | 全局安装包或模块,如果去掉global选项则表示本次项目安装 |
| yarn remove <包名> | 卸载/移除包或模块 |
| yarn | 根据在当前项目目录下的package.json的配置自动下载安装包列表,也可用yarn install |
| yarn dev | 根据在当前目录下的package.json文件的配置启动一个http服务器运行前端项目 |
| yarn init | 根据在当前目录下自动生成一个package.json配置文件,用于自动构建前端项目 |
| yarn publish | 把当前目录下文件根据package.json配置,作为一个开源包发布到官方维护的包管理器列表中。 |
package.json类似python的requirments.txt。
yarn默认的包管理服务器在国外,所以第一次使用yarn安装模块时会出现提示是否切换到淘宝镜像站,我们填写y即可。
2.4 安装Vue-cli¶
可以使用npm 或 yarn安装vue-cli 项目构想工具
npm install -g @vue/cli
# yarn global add @vue/cli # 注意,安装不成功就换成npm
# 安装完成以后,可以通过以下命令查看vue/cli的版本
vue -V
# 4.5.13
如果安装速度过慢,一直超时,可以考虑切换国内npm镜像源:http://npm.taobao.org/
2.5 使用Vue-CLI创建vue项目¶
2.5.1 生成vue项目¶
使用vue-cli自动化工具可以快速搭建单页应用项目目录。
该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目。安装过程:
2.5.1.1 默认安装¶
第一个选择安装配置,此处我们选择中间的(通过键盘上下键按钮移动),回车键:
第二个,选择包管理器。默认选择了Yarn,所以我们直接回车即可。
安装过程因为需要联网,所以如果出现网络安装失败,则重新安装多次。(4-5遍)
安装成功以后,效果如下:
跟着官方提示,输入命令。
打开蓝色链接地址,访问项目,http://localhost:8080:
2.5.1.2 自定义安装¶
第一,选择安装配置,最后一项就是选择自定义安装。回车。
第二,选择项目依赖文件。空格表示选择/取消选择。选择完成以后,回车。
第三,根据上面的配置,选择vue版本为3.0版本。
第四,选择路由Router的路由模式,问我们是否使用历史状态管理模型,就是路由没有#号。
第五,选择配置EsLint的配置项。
第六,ESLint格式化的时机。空格把2个都勾选上就行了。
第七,EsLint和Babel的配置是否保存一块,选择第二个选项。
第八,是否保存上面的一系列操作作为以后项目的安装配置。
后续的安装过程,和上面的选择的默认安装流程一致了。
2.6 使用Vite创建vue项目¶
vite是用于替代vue-cli而推出的新一代前端项目构建工具。在vue3.0版本中推荐我们使用vite构建vue项目。
目前,vue3.0支持使用的vite版本是2.0版本。如果我们开发的vue是2.0版本,则可以安装对应的vite1.0版本来使用。
vite 中文官网:https://cn.vitejs.dev/
2.6.1 vite安装和搭建vue项目¶
安装步骤:
设置项目目录名
选择生成项目的前端web框架。
选择js的语法类型,原生js和typescript2种。
- vue 表示选择使用原生javascript语法开发vue项目
- vue-ts 表示选择使用typescript扩展语法开发vue项目
接下来,通过代码编辑器,打开项目。
根据终端提示,安装项目运行的依赖模块。
终端下可以通过以下命令直接运行项目。
# yarn dev --host=0.0.0.0 --port=5173
vite --host=0.0.0.0 --port=5173 # 默认--host=127.0.0.1 --port=5173
也可以直接通过pycharm提供的方式来启动项目。
访问项目。默认端口就是5173,所以URL地址是:http://localhost:5173/
2.7 项目目录结构¶
项目根目录/
├── index.html # vue项目唯一的html页面,整个项目所有的内容都是被index.html加载提供给用户访问的
├── node_modules/ # 前端项目运行的依赖库目录,如果缺少则在项目根目录下,使用yarn或者npm update来恢复
├── package.json # 前端项目运行的依赖库的配置文件[通用],类似于python的requirements.txt
├── yarn.lock # 项目运行的依赖库的配置文件[yarn]
├── vite.config.js # 项目构建工具vite独有的运行配置文件,一般用于设置跨域代理
├── public/ # 访问入口目录,部分版本的vue的index.html入口文件会保存到这里,也可能不在这里
├── src/ # 项目的源码目录,是项目的核心,所有的源代码都保存在这里
├── App.vue # 项目的根组件,由App.vue进行加载所有的组件页面和子组件
├── assets/ # 资产目录,保存项目所需的静态资源[img/视频/音频等附件]
├── router/ # 路由文件存储目录,保存项目所有的路由文件
└── index.js # 默认的主路由文件,负责导入vue组件与url地址进行一对一的映射
├── components/ # 子组件目录,保存一些零零碎碎的部分页面内容的组件
└── main.js # 全局初始化入口脚本,index.html被运行时会自动加载这个js文件。
└── dist/ # 项目开发完成以后肯定要把项目部署上线,前端人员就需要编译整个项目,编译后的代码保存这里。
2.7.1 vue项目执行流程图¶
整个vue项目最开始用户访问的是主文件index.html,index.html中会引入src文件夹中的main.js,main.js中实例化vm对象,并导入顶级的单文件根组件src/App.vue,App.vue中会通过组件嵌套或者路由来引用页面组件,views组件会根据开发的页面需要加载components文件夹中的其他单文件子组件。
3. 单文件组件的使用¶
在vue等前端项目中,如果要导包引入其他文件内容,有三种格式内容可以被导入,分别是css、js、vue。src/App.vue,代码:
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
// 前端导入其他文件内容[css/js/vue]
// import "./style.css"
import settings from "./demo.js"
import {func, func2} from "./demo.js"
console.log(func() , func2());
console.log(settings.host);
import HelloWorld from './components/HelloWorld.vue'
</script>
src/demo.js,代码:
const settings = {
host: "http://www.baidu.com",
}
// 暴露一个对象,允许其他页面通过导包语句 from 对象名 import xxx.js
export default settings;
// 暴露一个函数,允许其他页面通过导包语句 from {func, func2} import xxx.js
export function func(){
return "helle!这是函数";
}
export function func2(){
return "helle!这是函数";
}
3.0 创建单文件组件¶
src/components目录下创建Home.vue。
组件文件名格式一般是大驼峰格式(每个单词首字母大写),以.vue结尾。
在组件中编辑三个标签,template标签中编写html内容、script标签操作vm对象和style标签编写css样式代码。
在script中因为vue发展过程中出现了2个不同的代码写法,所以存在了2个版本的script标签代码写法:传统的选项API与组合API。
其中,我们之前所学习的所有vue语法都是属于选项API写法,vue的所有代码全部被安排写到各种选项中,例如:data、methods、watch、created、updated、mounted、computed等都是选项。
然后在vue3.0以后,支持了一种新的代码组织方法,就是组合API的写法。我们后面再去学习,现在我们在Home.vue组件中使用原来选项API语法进行使用。
<template>
<div>
<h1>home组件的h1标签</h1>
<p @click="show">{{message}}</p>
<button @click="num--">-</button>
<input type="text" v-model.number="num" >
<button @click="num++">+</button>
</div>
</template>
<script>
export default {
name: "Home", // 设置组件名,一般建议和组件名保持一次,否则报错
data(){
return {
message: "hello, message",
num: 100,
}
},
methods: {
show(){
console.log(this.message);
}
}
}
</script>
<style scoped>
h1{
color: red;
}
</style>
上面的组件中的script标签中的代码就是选项API的写法。其他的2个标签,template与style标签中的内容不管选项API还是组合API,写法都一模一样,没有任何区别。
通过App.vue导包引入Home.vue,展示效果。src/App.vue,代码:
<script setup>
// import settings from "./demo.js"
// import {func, func2} from "./demo.js"
// console.log(func() , func2());
// console.log(settings.host);
// import HelloWorld from './components/HelloWorld.vue'
import Home from "./components/Home.vue"
</script>
<template>
<!-- <HelloWorld msg="Vite + Vue" />-->
<Home></Home>
<h1>App组件的h1标签</h1>
</template>
<style scoped>
</style>
3.1 template 编写html代码的地方¶
单个组件中template标签有且只能有一个。在vue2.0版本时代,template还必须只有有且只有一个子标签。
<template>
<div> <!-- vue2.0时代,必须外部增加一个div,保证template标签只有一个子元素 -->
<p @click="show">{{message}}</p>
<button @click="num--">-</button>
<input type="text" v-model.number="num" >
<button @click="num++">+</button>
</div>
</template>
3.2 script编写vue.js代码¶
单个组件中有且只有一个script标签,如果组件的代码风格使用的是选项API,则必须使用export default 把组件对象往外暴露。
如果组件的代码风格使用组合API,则script标签的开头部分必须是<script setup>。
<script>
export default {
name: "Home", // 设置组件名,一般建议和组件名保持一次,否则报错
data(){
return {
message: "hello, message",
num: 100,
}
},
methods: {
show(){
console.log(this.message);
}
}
}
</script>
3.3 style编写当前组件的css样式¶
单个组件内部,可以有0~1个style标签。如果存在style标签,style标签支持使用scoped属性来声明当前style标签内部的css样式是全局css样式还是组件内部的css样式。
上面的代码效果:
3.4 组件的嵌套¶
有时候开发vue项目时,一个组件肯定存在多个不同的板块内容,而往往一个页面中的多个板块内容都会设置一个个单独的子组件,同时页面也可以算是一个大组件,此时页面组件就是分成多个子组件,加载显示出现的。而一个组件引入1个或多个小的组件,这种情况,我们就称之为“组件的嵌套”。
例如,我们就可以声明一个页面组件保存目录src/views,并提供1个页面组件List.vue,List.vue作为页面组件,同时在src/components创建一个子组件Menu.vue。
在views目录下, 编写页面组件List.vue的代码。
List.vue,代码:
<template>
<h1>列表页</h1>
<Menu></Menu>
</template>
<script>
// 导入组件
import Menu from "../components/Menu.vue";
export default {
name: "List",
components: { // 局部注册子组件
Menu,// "Menu": Menu, 可以简写
}
}
</script>
<style scoped>
</style>
然后,在App.vue根组件中导入上面声明的List.vue页面组件。代码:
<script setup>
import List from "./views/List.vue";
</script>
<template>
<List></List>
</template>
<style scoped>
</style>
效果:
接着, List.vue页面组件中需要菜单组件,则可以导入Menu.vue。
src/components/Menu.vue,代码:
<template>
<div class="menu">
<a href="">首页</a>
<a href="">列表页</a>
<a href="">会员中心</a>
</div>
</template>
<script>
export default {
name: "Menu"
}
</script>
<style scoped>
.menu{
height: 80px;
overflow: hidden;
}
.menu a{
float: left;
height: 80px;
line-height: 80px;
text-align: center;
width: 140px;
background: blue;
color: #fff;
}
</style>
Home.vue引入Menu.vue的代码:
此时,页面的访问效果如下:
如果将来存在其他的页面组件也需要引入菜单组件,则可以按上面的流程作为参考。
例如:我们需要在App.vue中继续加载src/views/Register.vue注册页面组件。
src/views/Register.vue,代码:
<template>
<h1>注册页面</h1>
<Menu></Menu>
</template>
<script>
import Menu from "../components/Menu"
export default {
name: "Register",
components:{
Menu
}
}
</script>
<style scoped>
</style>
注册页面组件Regiter.vue,导入Menu.vue菜单子组件。
如果希望浏览器能显示Register祖册页面组件,则可以采用App.vue导入Register.vue,代码:
<script setup>
import List from "./views/List.vue";
import Register from "./views/Register.vue";
</script>
<template>
<List></List>
<Register></Register>
</template>
<style scoped>
</style>
最终效果:
上面虽然显示了注册页面的效果,但是同时也显示了List.vue页面组件的内容,在开发中一般不同的url地址应该显示不同的页面效果了。所以就需要实现url地址与页面组件之间的绑定映射关系了,这就是路由管理。那么,将来我们可以使用vue-router路由组件来完成,但是现在当前项目我们并没有安装这个插件,所以我们可以使用原生的js代码来简单实现路由管理功能。
路由的本质就是根据不同url地址判断显示对应的页面组件。src/App.vue,代码:
<script setup>
import List from "./views/List.vue";
import Register from "./views/Register.vue";
// 使用js提供的location对象,获取url地址的路径
// script声明的所有变量都会被自动填充到data中,可以直接在template模板标签中使用
let url = location.pathname;
</script>
<template>
<List v-if="url === '/list'"></List>
<Register v-if="url === '/reg'"></Register>
</template>
<style scoped>
</style>
经过上面的代码处理以后,就实现了简单的路由分发了。当浏览器访问/list就会显示List页面组件,访问/reg则会显示Register页面组件。当然,现在这种直接判断的方式并不专业,我们将来会在vue项目中安装并使用vue-router这种专业的路由插件来完成项目组件和url地址的映射关联。
此时,整个项目的嵌套结构:
3.5 组件之间的数据传递¶
3.5.1 父组件的数据传递给子组件¶
把父组件的数据传递给子组件。可以通过props属性来进行数据传递。
传递数据的2个步骤:
- 在父组件中调用子组件的组件名处,使用属性值的方式往子组件内部传递数据,src/views/List.vue
<template>
<h1>列表页</h1>
<p><input type="text" v-model="num"></p>
<!-- <Menu message="hello, 我是来自父组件List.vue的数据" :num="num" :data="[1,2,3]"></Menu>-->
<Menu message="hello, 我是来自父组件List.vue的数据" :num="num"></Menu>
</template>
<script>
// 导入组件
import Menu from "../components/Menu.vue";
export default {
name: "List",
data(){
return {
num: 100,
}
},
components: { // 局部注册子组件
Menu,// "Menu": Menu, 可以简写
}
}
</script>
<style scoped>
</style>
- 在子组件src/components/Menu.vue中接收父组件传递进来的数据,需要在script标签中,使用props属性接收。写法有3种。
代码:
<template>
<div class="menu">
<a href="">首页</a>
<a href="">列表页</a>
<a href="">会员中心</a>
</div>
<div>
子组件的内容的数据:
<p>{{message}}</p>
<p>num={{num}}</p>
<p>{{data}}</p>
</div>
</template>
<script>
// 数据类型
// Number : 数值类型
// String: 字符串类型
// Object: 对象类型
// Boolean: 布尔类型
// Array: 数组类型
// Date: 日期
// Function: 函数类型
export default {
name: "Menu",
// // 不限制数据类型与格式
// props: ["message", "num"],
// 限制数据类型与格式
// props: {
// message: String, // 此处表示希望接收来自父组件的字符串变量title
// num: Number,
// }
// 设置默认值或校验规则
props: {
message: {
type: String,
},
num: {
type: Number,
default: -1,
validator(value){
// 校验父组件传递进行的数据,是否符合条件,符合返回true,不符合返回false
if(value%2 === 0){
return true;
}
}
},
data: {
type: Object,
default: [],
required: true // 要求父组件必须传递的属性
}
}
}
</script>
<style scoped>
.menu{
height: 80px;
overflow: hidden;
}
.menu a{
float: left;
height: 80px;
line-height: 80px;
text-align: center;
width: 140px;
background: blue;
color: #fff;
}
</style>
步骤流程:
使用父组件传递数据给子组件时, 注意以下事项:
- 传递数据是变量,则需要在属性左边添加英文冒号.
传递数据是变量,这种数据称之为"动态数据传递",父组件数据改动的时候,子组件中被随之改动。
传递数据不是变量,这种数据称之为"静态数据传递"
- 来自props的数据,父组件中修改了数据,在子组件中会被同步修改。但是在子组件中如果修改了,因为不是data中声明的,所以父组件的数据不会发生变化,在开发时这种情况,也被称为"单向数据流"。
3.5.2 子组件传递数据给父组件¶
在子组件中通过this.$emit()来声明一个自定义的信号(vue中称之为自定义事件),父组件中针对这个信号(事件源)在调用子组件的组件名处进行@监听并接收数据。
src/components/Menu.vue,代码:
<template>
<div class="menu">
<a href="">首页</a>
<a href="">列表页</a>
<a href="">会员中心</a>
</div>
<div>
子组件的内容的数据:
<p>{{message}}</p>
<p>num={{num}},<input type="text" v-model="num"></p>
<p>{{data}}</p>
</div>
<div>
子组件中往父组件传递数据
<input type="text" v-model="text">
</div>
</template>
<script>
// 数据类型
// Number : 数值类型
// String: 字符串类型
// Object: 对象类型
// Boolean: 布尔类型
// Array: 数组类型
// Date: 日期
// Function: 函数类型
export default {
name: "Menu",
data(){
return {
text: "默认值"
}
},
watch: {
text(){
// 监听text如果发生变化,则往父组件传递数据
this.$emit("change_text", this.text); // 表示往父组件的change_text事件源传递1个数据,text变量
// this.$emit("change_text", 100, this.text); // 表示往父组件的change_text事件源传递2个数据,分别时100和text变量
}
},
// // 不限制数据类型与格式
// props: ["message", "num"],
// 限制数据类型与格式
// props: {
// message: String, // 此处表示希望接收来自父组件的字符串变量title
// num: Number,
// }
// 设置默认值或校验规则
props: {
message: {
type: String,
},
num: {
type: Number,
default: -1,
validator(value){
// 校验父组件传递进行的数据,是否符合条件,符合返回true,不符合返回false
if(value%2 === 0){
return true;
}
}
},
data: {
type: Object,
default: [],
required: true // 要求父组件必须传递的属性
}
}
}
</script>
<style scoped>
.menu{
height: 80px;
overflow: hidden;
}
.menu a{
float: left;
height: 80px;
line-height: 80px;
text-align: center;
width: 140px;
background: blue;
color: #fff;
}
</style>
和子组件中this.$emit("自定义事件名称")对应,父组件中声明一个同样名称的事件属性。
src/views/List.vue,代码:
<template>
<div>
<Menu :current="0" msg="首页" :num="num" @add_num="son_add"></Menu>
<h1>Home.vue</h1>
<button @click="num--" class="sub">-</button>
<input type="text" size="1" v-model="num">
<button @click="num++" class="add">+</button>
</div>
</template>
父组件中,声明一个方法用于在自定义事件发生时用于获取数据。
<template>
<h1>列表页</h1>
<p><input type="text" v-model="num"></p>
<!-- <Menu message="hello, 我是来自父组件List.vue的数据" :num="num" :data="[1,2,3]"></Menu>-->
<Menu @change_text="get_text" :message="msg" :num="num"></Menu>
<div>
<p>父组件接收来自子组件的数据,msg={{msg}}</p>
</div>
</template>
<script>
// 导入组件
import Menu from "../components/Menu.vue";
export default {
name: "List",
data(){
return {
num: 100,
msg: "",
}
},
methods: {
get_text(data){
// 此处就是一个普通方法,但是因为被绑定到了子组件的事件源,所以需要接收参数
this.msg = data;
}
},
components: { // 局部注册子组件
Menu,// "Menu": Menu, 可以简写
}
}
</script>
<style scoped>
</style>
练习:
1. 在父组件views/Goods.vue中的data选项中声明num1=10。传递到子组件components/Header.vue中显示num1的值出来。
提示:通过props接收num1,类型是Number
2. 在子组件components/Header.vue中的data选项中声明num2=50,监听num2,当num2的值发生变化,则把num2的值传递到父组件views/Goods.vue中并在data保存并显示到templates中。
提示:this.$emit("事件名", 变量1, 变量2...);
src/views/Goods.vue,代码:
<template>
<p>Goods.vue===>num1={{num1}},num={{num}}</p>
<input type="text" v-model="num1">
<Header :num="num1" @change_num2="get_value"></Header>
</template>
<script>
import Header from "../components/Header.vue";
export default {
name: "Goods",
data(){
return {
num1: 10,
num: 0,
}
},
methods: {
get_value(data){
this.num = data;
}
},
components: {Header,}
}
</script>
<style scoped>
</style>
src/App.vue,代码:
<script setup>
import List from "./views/List.vue";
import Register from "./views/Register.vue";
import Goods from "./views/Goods.vue";
// 使用js提供的location对象,获取url地址的路径
// script声明的所有变量都会被自动填充到data中,可以直接在template模板标签中使用
// let url = location.pathname;
</script>
<template>
<!-- <List v-if="url === '/list'"></List>-->
<!-- <Register v-if="url === '/reg'"></Register>-->
<Goods></Goods>
</template>
<style scoped>
</style>
src/components/Header.vue,代码:
<template>
<p>Header.vue接收来自父组件的数据,num={{num}}</p>
<input type="text" v-model="num2">
</template>
<script>
export default {
name: "Header",
props: ["num",],
data(){
return {
num2: 50,
}
},
watch:{
num2(){
this.$emit("change_num2", this.num2)
}
}
}
</script>
<style scoped>
</style>
4. 组合式API¶
Vue3.0在原来2.0版本基础上进行升级的时候,进行语法上的扩展与兼容。
4.1 快速使用¶
src/views/Group.vue,代码:
<template>
<button @click="add_student">添加学生</button>
<ul>
<li v-for="student in students.data">{{student}}</li>
</ul>
<button @click="courses.add_course">添加课程</button>
<ul>
<li v-for="course in courses.list">{{course}}</li>
</ul>
<button ref="p1" @click="get_p1">点击当前btn标签,获取当前标签对象</button>
</template>
<script setup>
import students from "../api/student";
import {add_student} from "../api/student";
import courses from "../api/courses";
/**
* ref引用template标签对象[抓取标签对象]
*/
import {ref} from "vue";
let p1 = ref(null);
const get_p1 = ()=>{
console.log(p1); // 这里的p1就是<p ref="p1">
console.log(p1.value); // 这里得到的就是原生的js的标签对象
}
</script>
<style scoped>
</style>
src/api/courses.js,代码:
import {ref, reactive} from "vue";
// courses: [], // 课程数据
export default reactive({
list: [
{"id":1, "name": "python"},
{"id":2, "name": "java"},
],
add_course(){
// 添加课程
console.log("添加课程");
}
})
export const get_course = (id)=>{
// 获取课程信息
}
export const del_course = ()=>{
// 删除课程
}
src/api/students.js,代码:
import {reactive} from "vue";
// students: [], // 学生数据
export default reactive({
data: [
{"id": 1, "name": "小明", "age": 17},
{"id": 2, "name": "小红", "age": 16},
]
});
export const add_student = ()=>{
// 添加学生
console.log("添加学生");
}
export const get_student = (id)=>{
// 获取学生信息
}
export const del_student = ()=>{
// 删除学生信息
}
src/App.vue,代码:
<script setup>
import Group from "./views/Group.vue"
</script>
<template>
<Group></Group>
</template>
<style scoped>
</style>
4.2 语法代码¶
4.2.1 基本语法¶
<template>
<h2>变量声明</h2>
<p>num1={{num1}}</p>
<input type="text" v-model="num1">
<hr>
<p>num2={{num2}}</p>
<input type="text" v-model="num2">
<ul>
<li v-for="item in data">{{item}}</li>
</ul>
{{student_info}}<br>
{{student_info.name}}<br>
<hr>
<h2>修改变量</h2>
<button @click="change_data1">修改ref数据</button>
<button @click="change_data2">修改reactive数据</button>
<hr>
<h2>计算属性</h2>
<input type="text" size="1" v-model.number="data1"> + <input type="text" v-model.number="data2"> = {{result}}
</template>
<script setup>
import {ref, reactive, watch, onMounted, computed} from "vue";
/**
* 声明只读变量
*/
let num1 = 10; // 声明一个只读变量,无法在template中进行双向数据绑定
/**
* 声明响应式变量
* ref函数的作用就是用于声明vue的基本数据类型[Number,String,Boolean/undefined/Array等]的响应式变量,可以进行双向数据绑定
* reactive 函数的作用就是用于声明vue的复杂数据类型[Object]的响应式变量,可以进行双向数据绑定
*/
let num2 = ref(100);
let data = ref([
{"id":1, "name": "小明", "age": 11},
{"id":2, "name": "小白", "age": 18},
{"id":3, "name": "小华", "age": 17},
{"id":4, "name": "小绿", "age": 18},
]);
let student_info = reactive({
"id": 1,
"name": "小白",
"age": 16
})
/**
* 修改变量的操作
*/
const change_data1 = ()=>{
// 修改ref响应式变量的值,需要通过value来改动
num2.value = 30000;
console.log(num2);
}
const change_data2 = ()=>{
// 修改reactive响应式变量的值,不能修改变量本身,可以修改变量内部的属性,直接赋值即可。
student_info.age = 20;
}
// 监听属性写法
watch(
num2, // 监听ref响应式变量,参数1是一个函数,返回值是被监听的ref变量的value属性
()=>{
// 值发生改变时自动调用的代码
console.log("num2改变了", num2);
}
)
watch(
student_info, // 监听reactive响应式变量,参数1就是被监听的reactive变量
()=>{
console.log("student_info改变了", student_info)
}
)
/**
* 钩子方法
*/
// created 钩子,vue初始化完成时自动自动的。但是在组合API中时没有created钩子的
// 原因开发者在 <script setup>标签中缩写的所有所有代码,默认都是在created钩子中执行的。
console.log("created执行了,此时vue刚完成初始化,还是渲染template")
// mounted钩子方法,在组合API中叫onMounted
onMounted(()=>{
// 视图被渲染以后,执行代码。
console.log("Mounted执行了,此时vue已经渲染了html内容");
})
/**
* computed 计算属性
*/
let data1 = ref(10);
let data2 = ref(20);
let result = computed(()=>{
return data1.value+data2.value;
});
</script>
<style scoped>
</style>
src/App.vue,代码:
<script setup>
import GroupApi from "./views/GroupApi.vue"
</script>
<template>
<GroupApi></GroupApi>
</template>
<style scoped>
</style>
4.2.2 参数传递¶
4.2.2.1 父传子¶
在父组件中,传递数据需要通过组件标签名的属性进行传递,而子组件中,通过defineProps接受父组件传递过来的数据。
src/views/User.vue,代码:
<template>
<h1>User父组件</h1>
<input type="text" v-model="str_data">
<input type="text" v-model.number="num_data">
<Info
:str_message="str_data"
:num_message="num_data"
:obj_data="obj_data"
></Info>
</template>
<script setup>
import {reactive, ref} from "vue";
import Info from "./Info.vue";
let str_data = ref("hello world");
let num_data = ref(10);
let obj_data = reactive({
id: 1,
name: "小明",
age: 18,
})
</script>
<style scoped>
</style>
src/views/Info.vue,代码:
<template>
<h1>Info子组件</h1>
<p>子组件中接收来自父组件的数据</p>
<p>{{props.str_message}}</p>
<p>{{props.num_message}}</p>
<p>obj_data={{props.obj_data}}</p>
</template>
<script setup>
// import {defineProps} from "vue";
// 使用defineProps来接收父组件的数据
const props = defineProps({
str_message: {
type: String,
default: "没有数据",
},
num_message: {
type: Number,
default: 0,
},
obj_data: Object,
})
</script>
<style scoped>
</style>
src/App.vue,代码:
<script setup>
import User from "./views/User.vue"
</script>
<template>
<User></User>
</template>
<style scoped>
</style>
4.2.2.2 子传父¶
在子组件中通过defineEmits先声明要注册的事件源,然后通过defineEmits的返回值emits进行调用事件源进行参数传递,在父组件中,直接声明方法绑定到子组件标签名中即可。
src/views/Info.vue,代码:
<template>
<h1>Info子组件</h1>
<p>子组件中接收来自父组件的数据</p>
<p>{{props.str_message}}</p>
<p>{{props.num_message}}</p>
<p>obj_data={{props.obj_data}}</p>
num=<input type="text" v-model="num">
</template>
<script setup>
// import {defineProps} from "vue";
import {ref, watch, defineEmits} from "vue";
// 注册事件源
const emits = defineEmits(['num_change'])
// 使用defineProps来接收父组件的数据
const props = defineProps({
str_message: {
type: String,
default: "没有数据",
},
num_message: {
type: Number,
default: 0,
},
obj_data: Object,
})
let num = ref(10);
watch(
num,
()=>{
console.log(num.value);
emits("num_change", num)
}
)
</script>
<style scoped>
</style>
src/views/User.vue,代码:
<template>
<h1>User父组件</h1>
<input type="text" v-model="str_data">
<input type="text" v-model.number="num_data">
<Info
:str_message="str_data"
:num_message="num_data"
:obj_data="obj_data"
@num_change="get_num"
></Info>
</template>
<script setup>
import {reactive, ref} from "vue";
import Info from "./Info.vue";
let str_data = ref("hello world");
let num_data = ref(10);
let obj_data = reactive({
id: 1,
name: "小明",
age: 18,
})
const get_num = (data)=>{
console.log("父组件中接收数据data=", data.value);
}
</script>
<style scoped>
</style>
5. axios¶
默认情况下,vue中并没有对ajax进行封装的,所以我们需要下载安装第三方httpajax工具包---axios。
官方文档:https://axios-http.com/zh/docs/intro
在项目根目录下打开终端,使用 npm或者yarn安装包
接着在src目录下创建一个utils/http.js脚本中,导入axios并通过create方法实例化一个http客户端请求对象,这样我们才能在vue的所有中组件直接使用axios发起http请求。
src/utils/http.js,代码:
import axios from "axios"; // 要导入安装的包,则直接填写包名即可。不需要使用路径
// 实例化
const http = axios.create({
baseURL: 'http://wthrcdn.etouch.cn/', // 请求的公共路径,一般填写服务端的默认的api地址,这个地址在具体使用的时候覆盖
timeout: 8000, // 最大请求超时时间,请求超过这个时间则报错,有文件上传的站点不要设置这个参数
headers: {'X-Custom-Header': 'foobar'} // 默认的预定义请求头,一般工作中这里填写隐藏了客户端身份的字段
});
export default http;
5.1 使用axios获取数据¶
src/views/Login.vue,代码:
<template>
<button @click="send_post">点击发送post请求</button>
<button @click="send_put">点击发送put请求</button>
<button @click="send_get">点击发送get请求</button>
<button @click="send_del">点击发送delete请求</button>
<hr>
<input type="text" v-model="city">
<button @click="get_weather">点击获取天气</button>
<div v-if="data_list">
<ul>
<li v-for="data in data_list">{{data}}</li>
</ul>
</div>
</template>
<script setup>
import http from "../utils/http";
import {ref} from "vue";
/**
* 发起post请求
*/
const send_post = ()=>{
http.post("/post", {
username: "小明",
age: 16,
sex: true,
},{
headers: {
Authorization: "xxxxx",
}
}).then(response=>{
console.log(response.data); // 服务端响应的响应体
}).catch(error=>{
console.log('请求错误!error=', error)
})
}
/**
* 如果发起的是put/patch,参考上面的post请求
*/
const send_put = ()=>{
http.put("/put", {
username: "小明",
age: 16,
sex: true,
},{
headers: {
Authorization: "xxxxx",
}
}).then(response=>{
console.log(response.data); // 服务端响应的响应体
}).catch(error=>{
console.log('请求错误!error=', error)
})
}
/**
* 发起get请求
*/
const send_get = ()=>{
http.get("/get",{
// 查询字符串,就是url地址后面?号后面的参数
params: {
city: "北京",
name: "小明",
},
headers:{
Authorization: "helazxm,dmmasdsamdmas",
}
}).then(response=>{
console.log(response.data)
}).catch(error=>{
console.log("请求发生错误!error=", error);
})
}
/**
* 如果要发起delete,则参考get请求写法
*/
const send_del = ()=>{
http.delete("/delete",{
// 查询字符串,就是url地址后面?号后面的参数
params: {
id: 10,
},
headers:{
Authorization: "helazxm,dmmasdsamdmas",
}
}).then(response=>{
console.log(response.data)
}).catch(error=>{
console.log("请求发生错误!error=", error);
})
}
// 点击获取天气信息
let city = ref("北京"); // 城市
let data_list = ref([]); // 天气列表
const get_weather = ()=>{
http.get("http://wthrcdn.etouch.cn/weather_mini",{
params: {
city: city.value,
}
}).then(response=>{
data_list.value = response.data.data?.forecast;
}).catch(error=>{
console.log("请求失败!error=", error);
})
}
</script>
<style scoped>
</style>
效果:
使用axios的时候,因为本质上来说axios还是javascript提供的ajax,所以也会受到同源策略的影响。
所谓的同源策略,实际上是浏览器为了保护客户端用户在服务端中的数据的安全所设置的一种网络安全机制/安全策略,
同源策略,可以禁止不同源下的客户端的js在没有得到服务端授权的情况下操作或调用服务端的数据。
所谓的同源,实际上就是判断通信的客户端与服务端,双方是否处于同一个协议,同一个域名/IP或同一个端口下,如果三者相同,则服务端和客户端属于同源,则同源策略不会拦截客户端的js请求服务端数据,如果三者中任意1个信息不一致,则属于不同源,不同源的客户端js无法访问和操作没有明确授权的服务端数据。
解决同源策略的方式有3种:CORS服务端授权、服务端代理、jsonp
CORS,翻译:跨域资源共享
5.2 请求和响应拦截器¶
官方文档:https://axios-http.com/zh/docs/interceptors
代码:
import axios from "axios";
// 实例化axios对象
const http = axios.create({
baseURL: 'http://httpbin.org', // 请求的公共路径,一般填写服务端的默认的api地址,这个地址在具体使用的时候覆盖
// timeout: 8000, // 最大请求超时时间,请求超过这个时间则报错,有文件上传的站点不要设置这个参数
// headers: {'X-Custom-Header': 'foobar'} // 默认的预定义请求头,一般工作中这里填写隐藏了客户端身份的字段
})
// 请求拦截器
http.interceptors.request.use(config=>{
// 编写 一些公共的请求代码 在 http请求发送之前
console.log("loading..., 请求头配置,config=", config)
return config;
}, ()=>{
// 编写一些公共的错误处理代码在请求发生错误时
alert("报错!报错!");
return Promise.reject(error);
})
// 响应拦截器
http.interceptors.response.use(response=>{
// 编写 一些公共代码在服务端响应数据成功时,响应状态是2xx
console.log("关闭loading...")
return response;
}, error=>{
// 编写一些公共代码在服务端响应错误时,响应状态码>=4xx
console.log("服务端告诉我们,请求出错了。");
return Promise.reject(error);
});
export default http;
6. 路由¶
路由(Router)是一种关系,是绑定了应用程序(页面组件)和url访问地址的一对一的映射关系。
在前端开发像vue这类的框架中,我们所听到的路由通常就是指代了帮开发者完成路由关系绑定的工具类/对象。
vue开发中,我们前端项目可以通过安装vue-router插件,方便的实现类似服务端的路由分发机制。可以根据url地址的路径不同,让vue项目显示不同的页面组件内容。
中文文档:https://next.router.vuejs.org/zh/
6.1 路由初始化¶
创建src/utils/router.js,代码:
import {createRouter, createWebHistory} from 'vue-router';
import Login from "../views/Login.vue";
const router = createRouter({
// history, 指定路由的模式, 此处默认使用的是hash模式, vue2.0版本中, mode: "history" / mode: "hash"
// createWebHashHistory hash模式 ===> http://localhost:5050/#/login
//[常用] createWebHistory history模式 ===> http://localhost:5050/login
// createMemoryHistory 带缓存的history模式 ===> http://localhost:5050/
history: createWebHistory(),
routes: [
{
path: "/", // 访问路径,必须以斜杠开头
name: "Login", // 组件别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址
},
{
path: "/login", // 访问路径,必须以斜杠开头
name: "myLogin", // 组件别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址,当多个地址绑定同一个组件,别名不能重复
},
{
path: "/goods",
name: "Goods",
component: import("../views/Goods.vue")
},
{
path: "/group/api",
name: "GroupApi",
component: import("../views/GroupApi.vue")
}
]
})
export default router;
注册路由对象到vue项目中,src/main.js,代码:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./utils/router";
createApp(App).use(router).mount('#app')
6.2 路由分发¶
router-view是vue-router提供的一个路由分发组件,这个组件可以根据不同的url地址,读取并加载路由列表中对应的组件。
src/App.vue,代码:
<script setup>
</script>
<template>
<!-- router-view的本质就是在内部使用location.pathname匹配判断url路径,加载不同的组件-->
<router-view></router-view>
</template>
<style scoped>
</style>
在vue-router被注册到vue项目时,vue-router会自动给vue组件对象内部新增2个路由操作的子对象
| 路由对象 | 描述 | 选项式API | 组合式API |
|---|---|---|---|
| 路由跳转对象 | 用于实现页面跳转效果 | this.$router | import { useRouter} from 'vue-router'const router = useRouter() |
| 路由参数对象 | 用于获取来自地址栏的参数 | this.$route | import { useRoute} from 'vue-router'const route = useRoute() |
6.3 页面跳转¶
vue-router提供了2种方式调用router进行页面跳转。
6.3.1 router-link组件跳转¶
router-link组件是vue-router组件提供的,本质上就是js替换页面部分内容,模拟页面跳转效果,不需要导包直接使用。
将来项目中,router-link用于跳转站内的页面,a标签用于跳转站外的页面
src/utils/router.js,代码:
import {createRouter, createWebHistory} from 'vue-router';
import Login from "../views/Login.vue";
const router = createRouter({
// history, 指定路由的模式, 此处默认使用的是hash模式, vue2.0版本中, mode: "history" / mode: "hash"
// createWebHashHistory hash模式 ===> http://localhost:5050/#/login
//[常用] createWebHistory history模式 ===> http://localhost:5050/login
// createMemoryHistory 带缓存的history模式 ===> http://localhost:5050/
history: createWebHistory(),
routes: [
{
path: "/", // 访问路径,必须以斜杠开头
name: "Login", // 组件别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址
},
{
path: "/login", // 访问路径,必须以斜杠开头
name: "myLogin", // 组件别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址,当多个地址绑定同一个组件,别名不能重复
},
{
path: "/goods",
name: "Goods",
component: import("../views/Goods.vue")
},
{
path: "/group/api",
name: "GroupApi",
component: import("../views/GroupApi.vue")
},
{
path: "/page/a",
name: "PageA",
component: import("../views/PageA.vue")
},
{
path: "/page/b",
name: "PageB",
component: import("../views/PageB.vue")
},{
path: "/page/c",
name: "PageC",
component: import("../views/PageC.vue")
},
]
})
export default router;
src/views/PageA.vue,代码:
<template>
<h1>pageA</h1>
<div>
<!-- a标签跳转页面会自动刷新页面 -->
<!-- <a href="/page/a">A页面</a>-->
<!-- <a href="/page/b">B页面</a>-->
<!-- <a href="/page/c">C页面</a>-->
<router-link to="/page/a">A页面</router-link>
<router-link to="/page/b">B页面</router-link>
<router-link to="/page/c">C页面</router-link>
</div>
</template>
<script setup>
</script>
<style scoped>
a{
color: blue;
}
.router-link-active{
text-decoration: none;
}
</style>
src/views/PageB.vue,代码:
<template>
<h1>pageB</h1>
<div>
<!-- a标签跳转页面会自动刷新页面 -->
<!-- <a href="/page/a">A页面</a>-->
<!-- <a href="/page/b">B页面</a>-->
<!-- <a href="/page/c">C页面</a>-->
<router-link to="/page/a">A页面</router-link>
<router-link to="/page/b">B页面</router-link>
<router-link to="/page/c">C页面</router-link>
</div>
</template>
<script setup>
</script>
<style scoped>
a{
color: blue;
}
.router-link-active{
text-decoration: none;
}
</style>
src/views/PageC.vue,代码:
<template>
<h1>pageC</h1>
<div>
<!-- a标签跳转页面会自动刷新页面 -->
<!-- <a href="/page/a">A页面</a>-->
<!-- <a href="/page/b">B页面</a>-->
<!-- <a href="/page/c">C页面</a>-->
<router-link to="/page/a">A页面</router-link>
<router-link to="/page/b">B页面</router-link>
<router-link to="/page/c">C页面</router-link>
</div>
</template>
<script setup>
</script>
<style scoped>
a{
color: blue;
}
.router-link-active{
text-decoration: none;
}
</style>
6.3.2 router对象跳转¶
标签跳转不会进行代码验证和拦截,因此可以使用vue-router组件提供的router对象实现跳转之前的验证和拦截。
<template>
<h1>pageA</h1>
<div>
<!-- a标签跳转页面会自动刷新页面 -->
<!-- <a href="/page/a">A页面</a>-->
<!-- <a href="/page/b">B页面</a>-->
<!-- <a href="/page/c">C页面</a>-->
<router-link to="/page/a">A页面</router-link>
<router-link to="/page/b">B页面</router-link>
<router-link to="/page/c">C页面</router-link>
</div>
<button @click="jump()">页面跳转到B页面</button>
</template>
<script setup>
import {useRouter} from "vue-router";
const router = useRouter()
const jump = ()=>{
// 在前面编写判断逻辑相关的代码,再决定是否跳转,一般用于完成表单验证。
// 使用url路径跳转,router.push("url路径");
// router.push("/page/b");
// router.push({"path": "/page/b"}); // 一般采用上面的简写
// 使用路由别名
// router.push({"name": "PageB"});
// 注意:router-link组件或者router对象都不能跳转到其他网站,只能是站内跳转
// router.push("http://www.baidu.com")
// // 返回上一页
// router.back()
// // 前进下一页
// router.forward()
// // 页面历史跳转
// router.go(-1); // 返回上1页,相当于 router.back()
// router.go(-2); // 返回上2页
// // router.go(-n); // 返回上n页
// router.go(1); // 前进下1页,相当于 router.forward()
// router.go(2); // 前进下2页
// router.go(n); // 前进n页
}
</script>
<style scoped>
a{
color: blue;
}
.router-link-active{
text-decoration: none;
}
</style>
6.4 参数传递和接收¶
在上面的页面跳转中, 不管我们使用的router-link组件标签,还是直接通过userRouter,实际上都是最终都是router对象来进行页面跳转的。那么除了router用于进行页面的跳转以外,在vue-router注册到vue框架以后,vue-router还提供了一个route对象给我们用于获取不同页面之间的参数传递和接收的操作。
route提供接收参数的方式有2种,分别是路由参数(路径参数,params)与查询参数(query)。
| 路由地址 | 组合式API | 选项式API |
|---|---|---|
/home/:id |
import {useRoute} from "vue-router"; const route = useRoute() route.params.id id就是路径参数名 |
this.$route.params.id id就是路径参数名 |
/home?name=xiaoming |
import {useRoute} from "vue-router"; const route = useRoute() route.query.name |
this.$route.query.name |
route.query.<变量名> # 接收页面跳转时,地址栏附带的查询字符串参数中指定变量名对应的值
/home?name=xiaoming # 接收xiaoming,则可以在代码: route.query.name
route.params.<路径别名> # 接收页面跳转时,地址栏中url路径中指定部分的路径别名对应的具体值
/user/100 # 接收100,则需要完成2件事件:
# 第1,在src/utils/router.js文件中,声明路由的格式为: path: "/user/:id"
# 第2,在页面跳转后,新页面中的js代码里面接收100,则 route.params.id
src/views/StudentList.vue,代码:
<template>
<h1>学生列表</h1>
<ul>
<li v-for="student in data_list"><router-link :to="`/students/${student.id}/${student.name}?age=${student.age}`">{{student.name}}</router-link></li>
</ul>
</template>
<script setup>
import {ref} from "vue";
let data_list = ref([
{"id":1, "name": "xiaoming", "age": 16,},
{"id":5, "name": "xiaohong", "age": 21,},
{"id":11, "name": "xiaobai", "age": 19,},
])
</script>
<style scoped>
</style>
src/views/StudentDetail.vue,代码:
<template>
<h1>{{route.params.name}}的信息详情</h1>
<p>接收路由参数,id={{route.params.id}}</p>
<p>接收查询参数,age={{route.query.age}}</p>
<!-- <button @click="back">返回上一页</button>-->
</template>
<script setup>
import {useRoute, useRouter} from "vue-router";
const route = useRoute()
// 接收所有的路由参数
console.log(route.params);
// 获取单个路由参数
console.log(route.params.id);
// import {useRouter} from "vue-router";
// const router = useRouter()
// const back = ()=>{
// // router.go(-1);
// router.back();
// }
</script>
<style scoped>
</style>
router/index.js,代码:
import {createRouter, createWebHistory} from 'vue-router';
import Login from "../views/Login.vue";
const router = createRouter({
// history, 指定路由的模式, 此处默认使用的是hash模式, vue2.0版本中, mode: "history" / mode: "hash"
// createWebHashHistory hash模式 ===> http://localhost:5050/#/login
//[常用] createWebHistory history模式 ===> http://localhost:5050/login
// createMemoryHistory 带缓存的history模式 ===> http://localhost:5050/
history: createWebHistory(),
routes: [
{
path: "/", // 访问路径,必须以斜杠开头
name: "Login", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址
},
{
path: "/login", // 访问路径,必须以斜杠开头
name: "myLogin", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址,当多个地址绑定同一个组件,别名不能重复
},
{
path: "/goods",
name: "Goods",
component: import("../views/Goods.vue")
},
{
path: "/group/api",
name: "GroupApi",
component: import("../views/GroupApi.vue")
},
{
path: "/page/a",
name: "PageA",
component: import("../views/PageA.vue")
},
{
path: "/page/b",
name: "PageB",
component: import("../views/PageB.vue")
},{
path: "/page/c",
name: "PageC",
component: import("../views/PageC.vue")
},{
path: "/students",
name: "StudentList",
component: import("../views/StudentList.vue")
},{
// path: "/students/:id/:name", // :变量名,表示必填参数,不限制任何类型[路由参数,也叫路径参数,StudentDetail.vue这个组件中可以通过useRoute的params属性来接收]
// path: "/students/:id([0-9]+)/:name", // :变量名后面跟着正则,表示必填参数,按正则匹配限制路由参数
path: "/students/:id([0-9]+)?/:name", // :变量名后面跟着正则,表示可选参数,按正则匹配限制路由参数
name: "StudentDetail",
component: import("../views/StudentDetail.vue")
},
]
})
export default router;
你可以在同一个路由中设置有多个路由参数,它们会映射到 route.params属性上。例如:
| 匹配模式(router/index.js) | 匹配路径(浏览器地址栏) | route.params(组件代码) |
|---|---|---|
/users/:username |
/users/xiaoming | { username: 'xiaoming' } |
/users/:username/posts/:id |
/users/xiaoming/posts/10 | { username: 'xiaoming', id: '10' } |
除了 route.params 之外,route 对象还公开了其他有用的信息,如 route.query(如果 URL 中存在参数)、route.hash 等。
6.5 嵌套路由¶
嵌套路由是vue-router提供给开发者应用在同一个页面组件下公共部分内容不变的情况下,切换不同子组件的时候使用的。
使用场景:admin后台运营站点,用户中心,活动界面等。
router/index.js,代码:
import {createRouter, createWebHistory} from 'vue-router';
import Login from "../views/Login.vue";
const router = createRouter({
// history, 指定路由的模式, 此处默认使用的是hash模式, vue2.0版本中, mode: "history" / mode: "hash"
// createWebHashHistory hash模式 ===> http://localhost:5050/#/login
//[常用] createWebHistory history模式 ===> http://localhost:5050/login
// createMemoryHistory 带缓存的history模式 ===> http://localhost:5050/
history: createWebHistory(),
routes: [
{
path: "/", // 访问路径,必须以斜杠开头
name: "Login", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址
},
{
path: "/login", // 访问路径,必须以斜杠开头
name: "myLogin", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址,当多个地址绑定同一个组件,别名不能重复
},
{
path: "/goods",
name: "Goods",
component: import("../views/Goods.vue")
},
{
path: "/group/api",
name: "GroupApi",
component: import("../views/GroupApi.vue")
},
{
path: "/page/a",
name: "PageA",
component: import("../views/PageA.vue")
},
{
path: "/page/b",
name: "PageB",
component: import("../views/PageB.vue")
},{
path: "/page/c",
name: "PageC",
component: import("../views/PageC.vue")
},{
path: "/students",
name: "StudentList",
component: import("../views/StudentList.vue")
},{
path: "/students/:id/:name", // :变量名,表示必填参数,不限制任何类型[路由参数,也叫路径参数,StudentDetail.vue这个组件中可以通过useRoute的params属性来接收]
// path: "/students/:id([0-9]+)/:name", // :变量名后面跟着正则,表示必填参数,按正则匹配限制路由参数
// path: "/students/:id([0-9]+)?/:name", // :变量名后面跟着正则,表示可选参数,按正则匹配限制路由参数
name: "StudentDetail",
component: import("../views/StudentDetail.vue")
},
{
path: "/admin", // 作为顶级路由,务必以/开头
name: "Admin",
component: import("../views/admin/Main.vue"),
children: [ // 子组件的路由列表
{
path: "user", // 作为子路由存在时,path绝不能以/开头!!!
name: "User",
component: import("../views/admin/User.vue")
},
{
path: "goods",
name: "AdminGoods",
component: import("../views/admin/Goods.vue")
}
]
}
]
})
export default router;
src/views/Admin/Main.vue,代码:
<template>
<div class="page">
<div class="header">
头部
</div>
<div class="main">
<div class="menu">
<ul>
<li><router-link to="/admin/user">用户管理</router-link></li>
<li><router-link to="/admin/goods">商品管理</router-link></li>
</ul>
</div>
<div class="content">
<router-view></router-view>
</div>
</div>
<div class="footer">
脚部
</div>
</div>
</template>
<script setup>
</script>
<style scoped>
.header, .footer{
height: 100px;
background: #ccc;
}
.main{
background: #ddd;
}
.menu{
float: left;
background: #aaaaff;
width: 300px;
min-height: 400px;
}
.main:after{
display: block;
clear: both;
content: "";
}
</style>
src/views/Admin/User.vue,代码:
src/views/Admin/Goods.vue,代码:
可以通过以下地址分别访问用户中心的不同页面内容:
http://127.0.0.1:5173/admin/user ---> src/views/admin/User.vue
http://127.0.0.1:5173/admin/goods ---> src/views/admin/Goods.vue
6.6 路由守卫¶
路由守卫:也叫导航守卫,功能作用类似于上面的axios的拦截器。我们可以基于导航守卫编写一些在页面跳转过程中需要编写的公共代码,例如:权限的验证,页面的公共信息初始化,页面跳转之前的一些公共逻辑。不同的是axios的拦截器是拦截http请求或相应的,而路由守卫拦截的本地vue的页面跳转。
6.6.1 全局前置守卫¶
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
src/router/index.js,代码:
import {createRouter, createWebHistory} from 'vue-router';
import Login from "../views/Login.vue";
const router = createRouter({
// history, 指定路由的模式, 此处默认使用的是hash模式, vue2.0版本中, mode: "history" / mode: "hash"
// createWebHashHistory hash模式 ===> http://localhost:5050/#/login
//[常用] createWebHistory history模式 ===> http://localhost:5050/login
// createMemoryHistory 带缓存的history模式 ===> http://localhost:5050/
history: createWebHistory(),
routes: [
{
path: "/", // 访问路径,必须以斜杠开头
name: "Login", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址
},
{
path: "/login", // 访问路径,必须以斜杠开头
name: "myLogin", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址,当多个地址绑定同一个组件,别名不能重复
},
{
path: "/goods",
name: "Goods",
component: import("../views/Goods.vue")
},
{
path: "/group/api",
name: "GroupApi",
component: import("../views/GroupApi.vue")
},
{
path: "/page/a",
name: "PageA",
component: import("../views/PageA.vue")
},
{
path: "/page/b",
name: "PageB",
component: import("../views/PageB.vue")
},{
path: "/page/c",
name: "PageC",
component: import("../views/PageC.vue")
},{
path: "/students",
name: "StudentList",
component: import("../views/StudentList.vue")
},{
path: "/students/:id/:name", // :变量名,表示必填参数,不限制任何类型[路由参数,也叫路径参数,StudentDetail.vue这个组件中可以通过useRoute的params属性来接收]
// path: "/students/:id([0-9]+)/:name", // :变量名后面跟着正则,表示必填参数,按正则匹配限制路由参数
// path: "/students/:id([0-9]+)?/:name", // :变量名后面跟着正则,表示可选参数,按正则匹配限制路由参数
name: "StudentDetail",
component: import("../views/StudentDetail.vue")
},
{
path: "/admin", // 作为顶级路由,务必以/开头
name: "Admin",
component: import("../views/admin/Main.vue"),
children: [ // 子组件的路由列表
{
meta: {
authentication: true,
title: "用户管理"
},
path: "user", // 作为子路由存在时,path绝不能以/开头!!!
name: "User",
component: import("../views/admin/User.vue")
},
{
meta: {
authentication: false,
title: "商品管理"
},
path: "goods",
name: "AdminGoods",
component: import("../views/admin/Goods.vue")
}
]
}
]
})
/**
* 全局前置守卫
* 在路由跳转之前进行拦截
*/
// // 写法1:
// router.beforeEach((to, from)=>{
// console.log(to); // 表示下一个页面的路由对象
// console.log(from); // 表示当前页面的路由对象
//
// if(to.meta.authentication){
// // 阻止页面跳转了。
// // return false;
//
// // 重定向跳转到登录页面
// return { name: 'Login' }
// }
//
// })
// 写法2:
router.beforeEach((to, from,next)=>{
if(to.meta.authentication){
return next({path: "/login"})
}
next();
})
export default router;
6.6.2 全局后置守卫¶
代码:
import {createRouter, createWebHistory} from 'vue-router';
import Login from "../views/Login.vue";
const router = createRouter({
// history, 指定路由的模式, 此处默认使用的是hash模式, vue2.0版本中, mode: "history" / mode: "hash"
// createWebHashHistory hash模式 ===> http://localhost:5050/#/login
//[常用] createWebHistory history模式 ===> http://localhost:5050/login
// createMemoryHistory 带缓存的history模式 ===> http://localhost:5050/
history: createWebHistory(),
routes: [
{
path: "/", // 访问路径,必须以斜杠开头
name: "Login", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址
},
{
path: "/login", // 访问路径,必须以斜杠开头
name: "myLogin", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址,当多个地址绑定同一个组件,别名不能重复
},
{
path: "/goods",
name: "Goods",
component: import("../views/Goods.vue")
},
{
path: "/group/api",
name: "GroupApi",
component: import("../views/GroupApi.vue")
},
{
path: "/page/a",
name: "PageA",
component: import("../views/PageA.vue")
},
{
path: "/page/b",
name: "PageB",
component: import("../views/PageB.vue")
},{
path: "/page/c",
name: "PageC",
component: import("../views/PageC.vue")
},{
path: "/students",
name: "StudentList",
component: import("../views/StudentList.vue")
},{
path: "/students/:id/:name", // :变量名,表示必填参数,不限制任何类型[路由参数,也叫路径参数,StudentDetail.vue这个组件中可以通过useRoute的params属性来接收]
// path: "/students/:id([0-9]+)/:name", // :变量名后面跟着正则,表示必填参数,按正则匹配限制路由参数
// path: "/students/:id([0-9]+)?/:name", // :变量名后面跟着正则,表示可选参数,按正则匹配限制路由参数
name: "StudentDetail",
component: import("../views/StudentDetail.vue")
},
{
path: "/admin", // 作为顶级路由,务必以/开头
name: "Admin",
component: import("../views/admin/Main.vue"),
children: [ // 子组件的路由列表
{
meta: {
authentication: true,
title: "用户管理"
},
path: "user", // 作为子路由存在时,path绝不能以/开头!!!
name: "User",
component: import("../views/admin/User.vue")
},
{
meta: {
authentication: false,
title: "商品管理"
},
path: "goods",
name: "AdminGoods",
component: import("../views/admin/Goods.vue")
}
]
}
]
})
/**
* 全局前置守卫
* 在路由跳转之前进行拦截
*/
// // 写法1:
// router.beforeEach((to, from)=>{
// console.log(to); // 表示下一个页面的路由对象
// console.log(from); // 表示当前页面的路由对象
//
// if(to.meta.authentication){
// // 阻止页面跳转了。
// // return false;
//
// // 重定向跳转到登录页面
// return { name: 'Login' }
// }
//
// })
// // 写法2:
// router.beforeEach((to, from,next)=>{
// if(to.meta.authentication){
// return next({path: "/login"})
// }
// next();
// })
// 全局后置守卫
// 在页面跳转之后的完成一些页面的初始化工作
router.afterEach((to, from) => {
console.log("to=", to) // 下一页页面的路由操作对象
console.log("from=", from) // 当前页面的路由对象
document.title = to.meta.title // 例如,把meta中的title作为页面标题进行展示
})
export default router;
6.6.3 路由独享守卫¶
const routes = [ // 路由列表
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
路由,代码:
import {createRouter, createWebHistory} from 'vue-router';
import Login from "../views/Login.vue";
const router = createRouter({
// history, 指定路由的模式, 此处默认使用的是hash模式, vue2.0版本中, mode: "history" / mode: "hash"
// createWebHashHistory hash模式 ===> http://localhost:5050/#/login
//[常用] createWebHistory history模式 ===> http://localhost:5050/login
// createMemoryHistory 带缓存的history模式 ===> http://localhost:5050/
history: createWebHistory(),
routes: [
{
path: "/", // 访问路径,必须以斜杠开头
name: "Login", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址
},
{
path: "/login", // 访问路径,必须以斜杠开头
name: "myLogin", // 路由别名[与组件文件名同名]
component: Login, // 组件对象,一个组件可以绑定多个url地址,当多个地址绑定同一个组件,别名不能重复
},
{
path: "/goods",
name: "Goods",
component: import("../views/Goods.vue")
},
{
path: "/group/api",
name: "GroupApi",
component: import("../views/GroupApi.vue")
},
{
path: "/page/a",
name: "PageA",
component: import("../views/PageA.vue")
},
{
path: "/page/b",
name: "PageB",
component: import("../views/PageB.vue")
},{
path: "/page/c",
name: "PageC",
component: import("../views/PageC.vue")
},{
path: "/students",
name: "StudentList",
component: import("../views/StudentList.vue")
},{
path: "/students/:id/:name", // :变量名,表示必填参数,不限制任何类型[路由参数,也叫路径参数,StudentDetail.vue这个组件中可以通过useRoute的params属性来接收]
// path: "/students/:id([0-9]+)/:name", // :变量名后面跟着正则,表示必填参数,按正则匹配限制路由参数
// path: "/students/:id([0-9]+)?/:name", // :变量名后面跟着正则,表示可选参数,按正则匹配限制路由参数
name: "StudentDetail",
component: import("../views/StudentDetail.vue")
},
{
path: "/admin", // 作为顶级路由,务必以/开头
name: "Admin",
component: import("../views/admin/Main.vue"),
children: [ // 子组件的路由列表
{
meta: {
authentication: true,
title: "用户管理"
},
path: "user", // 作为子路由存在时,path绝不能以/开头!!!
name: "User",
component: import("../views/admin/User.vue")
},
{
meta: {
authentication: false,
title: "商品管理"
},
path: "goods",
name: "AdminGoods",
component: import("../views/admin/Goods.vue")
}
]
},
{
path: "/list",
name: "List",
component: import("../views/List.vue"),
beforeEnter: (to, from) => {
console.log(`from===>`, from);
console.log(`to===>`, to);
console.log(`to===>`, to.query.id); // 不管是from还是to,都是路由对象,都可以使用query或者params来提取路由参数
// 返回值如果是true则表示直接允许跳转,返回false表示拦截不让页面跳转
return true;
},
}
]
})
/**
* 全局前置守卫
* 在路由跳转之前进行拦截
*/
// // 写法1:
// router.beforeEach((to, from)=>{
// console.log(to); // 表示下一个页面的路由对象
// console.log(from); // 表示当前页面的路由对象
//
// if(to.meta.authentication){
// // 阻止页面跳转了。
// // return false;
//
// // 重定向跳转到登录页面
// return { name: 'Login' }
// }
//
// })
// // 写法2:
// router.beforeEach((to, from,next)=>{
// if(to.meta.authentication){
// return next({path: "/login"})
// }
// next();
// })
// 全局后置守卫
// 在页面跳转之后的完成一些页面的初始化工作
router.afterEach((to, from) => {
console.log("to=", to) // 下一页页面的路由操作对象
console.log("from=", from) // 当前页面的路由对象
document.title = to.meta.title
})
export default router;
7. 打包编译¶
不管我们使用的是vue-cli还是vite进行项目构建和管理,将来要进行项目的部署迁移到外网服务器时,都是需要使用打包后的项目代码,打包后的代码会进行压缩,并且只需要运行在http服务器下面即可。我们一般在公司里面往往使用nginx来运行打包后的web前端项目。
因为我们暂时还没有学习到nginx,所以我们使用前端开发人员经常用于进行项目测试的live-server测试服务器来运行打包后的vue项目代码。
运行项目。









































