Vue2
渐进式
什么是库和框架
库: 封装的属性或方法 (例jquery.js)
框架: 拥有自己的规则和元素, 比库强大的多 (例vue.js)
vue开发方式
- 传统开发模式:基于html/css/js文件开发vue
- 工程化开发方式:在webpack环境中开发vue,这是最推荐, 企业常用的方式
@vue/cli脚手架
@vue/cli是Vue官方提供的一个全局模块包(得到vue命令), 此包用于创建脚手架项目
脚手架是为了保证各施工过程顺利进行而搭设的工作平台
@vue/cli模块包安装
把@vue/cli模块包按到全局, 电脑拥有vue命令, 才能创建脚手架工程
// 全局安装命令
yarn global add @vue/cli
# 或
npm install -g @vue/cli
查看vue脚手架版本
vue -V
// 如果出现版本号就安装成功, 否则失败
@vue/cli 创建项目启动服务
注意: 项目名不能带大写字母, 中文和特殊符号
创建项目
// vue和create是命令, vuecli-demo是文件夹名
vue create vuecli-demo //不能中文、大写字母、特殊符号
选择模板
上下箭头选择 Vue 2 的那个
Default ([Vue 2] babel, eslint) // 选择这条
选择下载依赖包的方式
都可
Use Yarn
Use NPM
回车等待生成项目文件夹+文件+下载必须的第三方包们
进入脚手架项目下, 启动内置的热更新本地服务器
cd vuecil-demo // 进入脚手架项目下
npm run serve // 启动内置的热更新本地服务器
# 或
yarn serve
// 看到以下 表示成功
DONE Compiled successfully in 4226ms // 此行绿色
App running at:
- Local: http://localhost:8080/
- Network: http://172.20.10.5:8080/
Note that the development build is not optimized.
To create a production build, run yarn build.
打开浏览器输入上述地址
@vue/cli 目录和代码分析
vuecil-demo # 项目目录
├── node_modules # 项目依赖的第三方包
├── public # 静态文件目录
├── favicon.ico# 浏览器小图标
└── index.html # 单页面的html文件(网页浏览的是它)
├── src # 业务文件夹
├── assets # 静态资源
└── logo.png # vue的logo图片
├── components # 组件目录
└── HelloWorld.vue # 欢迎页面vue代码文件
├── App.vue # 整个应用的根组件
└── main.js # 入口js文件
├── .gitignore # git提交忽略配置
├── babel.config.js # babel配置
├── package.json # 依赖包列表
├── README.md # 项目说明
└── yarn.lock # 项目包版本锁定和缓存地址
主要文件及含义
node_modules下都是下载的第三方包
public/index.html – 浏览器运行的网页
src/main.js – webpack打包的入口文件
src/App.vue – vue项目入口页面
package.json – 依赖包列表文件
@vue/cli 自定义配置
项目中没有webpack.config.js文件,因为@vue/cli用vue.config.js
src并列处新建vue.config.js
/* 覆盖webpack的配置 */
module.exports = defineConfig({
transpileDependencies: true,
devServer: { // 自定义服务配置
open: true, // 自动打开浏览器
port: 8888, // 更改端口号
host: 'localhost'
}
})
@vue/cli 单vue文件讲解
单vue文件好处, 独立作用域互不影响
Vue推荐采用.vue文件来开发项目
template里只能有一个根标签
vue文件-独立模块-作用域互不影响
style配合scoped属性, 保证样式只针对当前template内标签生效
vue文件配合webpack, 把他们打包起来插入到index.html
<!-- template必须, 只能有一个根标签, 影响渲染到页面的标签结构 -->
<template>
<div>欢迎使用vue</div>
</template>
<!-- js相关 -->
<script>
export default {
name: 'App'
}
</script>
<!-- 当前组件的样式, 设置scoped, 可以保证样式只对当前页面有效 -->
<style scoped>
</style>
Vue指令
vue基础-插值表达式
在dom标签中, 直接插入内容
又叫: 声明式渲染/文本插值
语法: {{ 表达式 }}
<template>
<div>
<!-- 把值赋到标签 -->
<h1>{{ msg }}</h1>
<h2>{{ obj.name }}</h2>
<h3>{{ obj.age > 18 ? '成年' : '未成年' }}</h3>
</div>
</template>
<script>
export default {
// 变量要在data函数return的对象里声明
data() { // 格式固定, 定义vue数据之处
return { // key相当于变量名
msg: "hello, vue",
obj: {
name: "小vue",
age: 5
}
}
}
}
</script>
<style>
</style>
vue指令 v-bind 属性操作
- 语法:
v-bind:属性名="vue变量名"
- 简写:
:属性名="vue变量名"
<!-- vue指令-v-bind属性动态赋值 -->
<a v-bind:href="url">我是a标签</a>
<img :src="imgSrc">
<!-- imgSrc 在data函数return的对象里声明 值为地址 -->
vue指令 v-on 绑定事件
- 语法
- v-on:事件名=“要执行的少量代码”
- v-on:事件名=“methods中的函数”
- v-on:事件名=“methods中的函数(实参)”
- 简写: @事件名=“methods中的函数”
<!-- vue指令: v-on事件绑定-->
<p>你要买商品的数量: {{count}}</p>
<button v-on:click="count = count + 1">增加1</button>
<button v-on:click="addFn">增加1个</button>
<button v-on:click="addCountFn(5)">一次加5件</button>
<button @click="subFn">减少</button>
<script>
export default {
data() { // 格式固定, 定义vue数据之处
return {
count:1
}
}, // 定义函数
methods: {
addFn(){ // this代表export default后面的组件对象(下属有data里return出来的属性)
this.count++
},
addCountFn(num){
this.count += num
},
subFn(){
this.count--
}
}
}
</script>
阻止默认行为
- 无传参, 通过形参直接接收
- 传参, 通过$event指代事件对象传给事件处理函数
<a @click="one" href="http://www.baidu.com">百度</a>
// 有参数时 $event 代表 e
<a @click="two(10, $event)" href="http://www.baidu.com">百度</a>
methods: {
one(e){
e.preventDefault()
},
two(num, e){
e.preventDefault()
}
}
vue指令-v-on事件修饰符 阻止行为
- @事件名.修饰符=“methods里函数”
- .stop - 阻止事件冒泡
- .prevent - 阻止默认行为
- .once - 程序运行期间, 只触发一次事件处理函数
vue指令-v-on按键修饰符 监听按键事件
- @键盘事件.enter - 监测回车按键
- @键盘事件.esc - 监测返回按键
<input type="text" @keydown.enter="enterFn">
当按下的是回车键 才会执行 enterFn() 函数
vue指令 v-model 双向数据绑定
- 语法: v-model=“vue变量名”
- 双向数据绑定
- 数据变化 -> 视图自动同步
- 视图变化 -> 数据自动同步
<!-- 输入框 -->
<input type="text" v-model="username" />
data() {
return {
username: "",
};
<!-- 下拉框 -->
<div>
<span>来自于:</span>
<select v-model="from">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
</select>
</div>
data() {
return {
from: "",
};
<!-- 复选框 -->
<!-- 遇到复选框,v-model的变量值是非数组时-关联的是复选框的checked属性,是数组时-关联的是复选框的value属性 -->
<div>
<span>爱好:</span>
<input type="checkbox" v-model="hobby" value="抽烟" />抽烟
<input type="checkbox" v-model="hobby" value="喝酒" />喝酒
<input type="checkbox" v-model="hobby" value="烫头" />烫头
</div>
data() {
return {
hobby:[],
};
<!-- 单选框 -->
<div>
<span>性别:</span>
<input type="radio" name="sex" v-model="sex" value="男" />男
<input type="radio" name="sex" v-model="sex" value="女" />女
</div>
data() {
return {
sex:'',
};
<!-- 大文本框 -->
<div>
<span>自我介绍</span><br />
<textarea v-model="intro"></textarea>
</div>
data() {
return {
intro: ''
};
};
双向数据绑定扩展写法
<input type="text" v-on:input='msg=$event.target.value' :value='msg' />
data() {
return {
msg: "",
};
vue指令 v-model 修饰符
v-model.修饰符=“vue数据变量”
- .number 以parseFloat转成数字类型
- .trim 去除首尾空格 但不会去处中间的空格
- .lazy 在失去焦点时才同步
<!-- .number写文字字符串并不会转成数值型 -->
<input type="text" v-model.number="age" />
<input type="text" v-model.trim="motto" />
<textarea v-model.lazy="intro"></textarea>
vue指令 v-text和v-html
- 语法:
- v-text=“vue数据变量”
- v-html=“vue数据变量”
- 注意: 会覆盖插值表达式
<p v-text="str"></p>
// <span>我是一个span标签</span>
<p v-html="str"></p>
// 我是一个span标签
data() {
return {
str: "<span>我是一个span标签</span>"
}
}
vue指令 v-show和v-if
控制元素显示隐藏
- 语法:
- v-show=“vue变量(true)”
- v-if=“vue变量(true)”
- 原理
- v-show 用的display:none隐藏 (频繁切换使用)
- v-if 直接从DOM树上移除 删除标签
- 高级
- v-else使用
// 显示隐藏
<h1 v-show="isOk">v-show的盒子</h1>
<h1 v-if="isOk">v-if的盒子</h1>
// if判断
<div>
<p v-if="age < 18">我未成年</p>
<p v-else-if="age >= 18 && age > 30">老了老了</p>
<p v-else>我成年了</p>
</div>
data() {
return {
isOk: true,
age: 15
}
}
频繁切换使用 v-show
不会频繁切换使用 v-if 减少页面加载时间
vue指令-v-for 循环
语法
- v-for=“(值, 索引) in 目标结构”
- v-for=“值 in 目标结构”
目标结构:
- 可以遍历数组 / 对象 / 数字 / 字符串 (可遍历结构)
注意:
v-for的临时变量名不能用到v-for范围外
注意:想让谁循环生成 v-for就写在谁身上
<!-- 遍历数组 -->
<ul>
<li v-for="(item, index) in arr" :key="index"> {{ item }}---{{ index }}
</li>
</ul>
data() {
return {
arr: ['小明', '小黑', '小白']
};
},
<!-- 遍历对象 -->
<ul>
<li v-for="(k, key) in obj" :key="key">
{{ k }}---{{key}}
</li>
</ul>
obj: {
uname: '小明',
age: 18,
sex: '男'
}
v-for更新监测
当v-for遍历的目标结构改变, Vue触发v-for的更新
口诀:
能改变原数组的方法, 就会导致v-for更新, 页面更新
数组非变更方法, 返回新数组的, 就不会导致v-for更新, 可采用覆盖数组或 this.$set() 改变某一个值
数组变更方法
// 这些方法会触发数组改变, v-for会监测到并更新页面
- push() // 添加元素
- pop() // 删除数组里最后一个
- shift() // 删除数组里第一个
- unshift() // 数组开头添加
- splice() // 根据索引删除元素 或 插入元素
- sort() // 数组排序
- reverse() // 翻转数组
// 这些方法不会触发v-for更新 返回新数组
slice() // 截取数组
filter() // 遍历返回符合指定条件的所有元素
concat() // 合并数组 连接数组
数组变更值 this.$set()
vue不能监测到数组里赋值的动作而更新, 如果需要用this.$set() 方法, 或者覆盖整个数组
this.$set() // 三个参数 要改的结构,要改的位置,值
this.$set(this.arr, 0, 1000);
v-for就地更新
v-for
的默认行为会尝试原地修改元素而不是移动它们。
vue基础_虚拟dom
本质是保存dom信息、属性、内容的一个js 对象
旧虚拟dom和新虚拟dom在内存中比较哪里发生了变化
更新对应的真实dom
-
比如template里标签结构
<template> <div id="box"> <p class="my_p">123</p> </div> </template>
对应的虚拟DOM结构
const dom = { type: 'div', attributes: [{id: 'box'}], children: { type: 'p', attributes: [{class: 'my_p'}], text: '123' } }
提高了更新DOM的性能(不用把页面全删除重新渲染)
虚拟DOM只包含必要的属性(没有真实DOM上百个属性)
vue基础_diff算法-key
情况1: 根元素变了, 删除重建
旧虚拟DOM
<div id="box">
<p class="my_p">123</p>
</div>
新虚拟DOM
<ul id="box">
<li class="my_p">123</li>
</ul>
情况2: 根元素没变, 属性改变, 元素复用 更新属性
旧虚拟DOM
<div id="box">
<p class="my_p">123</p>
</div>
新虚拟DOM
<div id="myBox" title="标题">
<p class="my_p">123</p>
</div>
情况3:元素内容发生改变
会分diff的2种情况比较 (有key / 无key)
// 有key 或 key为索引的时候 就地更新
// key为id,按照 key 比较
// key值得要求是 ‘唯一的不重复的字符串或数值’
// key怎么用 :有id用id 无id用索引
// key的好处 :可以配合虚拟DOM提高更新的性能
vue基础 动态class 类名
语法:
- :class=“{类名: 布尔值}”
- :class=“{类名: 布尔值 , 类名: 布尔值}” 多个类名,分隔
动态class
<p :class="{red_str: bool}">动态class</p>
data(){
return {
bool: true
}
}
// 布尔值为true 类名才生效
固定class
// 固定class 和 动态class 可以共用
<p class="cat" :class="{red_str: bool}">动态class</p>
class数组写法
// 数组里面是变量名
<p :class="[Class,Classs]">111</p>
data() {
return {
// 变量值为class类名 一个变量里可写多个空格分隔
Class: 'act ae',
Classs:'actt'
};
},
三元写法
<p :class="bool ? 'act' : 'aet'"></p>
data(){
return {
bool: true
}
}
vue基础-动态style
语法
- :style=“{css属性: ’ 值 ’ }”
- :style=“{css属性: 变量}”
<p :style="{background:'red'}">12345</p>
<p :style="{background: color }">12345</p>
data(){
return {
color: 'red'
}
}
style 对象写法
<p :style="obj">对象写法</p>
data(){
return {
obj: {
color: 'orange',
background: 'red'
}
}
}
style 数组写法
<div :style="[colors, styles]">数组写法</div>
data(){
return {
colors: {
color: 'orange',
background: 'red'
},
styles: {
width: '100px',
height: '100px',
fontSize:'20px' // 驼峰命名
}
}
}
vue过滤器
转换格式, 过滤器就是一个函数, 传入值返回处理后的值
只能用在差值表达式和v-bind:动态属性里面
-
Vue.filter(“过滤器名”, (值) => {return “返回处理后的值”})
-
filters: {过滤器名字: (值) => {return “返回处理后的值”}
全局过滤器
Vue.filter(“过滤器名”, (值) => {return “返回处理后的值”})
//全局过滤器定义在 main.js 里 所有.vue页面都可以用
Vue.filter('reverse',(val)=>{return val.split('').reverse().join('') })
// 使用语法: {{ 值(变量名) | 过滤器名字 }}
<div>{{msg | reverse}}</div> // 差值表达式
data(){
return {
msg:'虾仁不眨眼'
}
}
局部过滤器
filters: {过滤器名字: (值) => {return “返回处理后的值”}
// 局部过滤器定义在 .vue 里 只有在当前页面可用
// 使用语法: {{ 值(变量名) | 过滤器函数名字 }}
<div>{{msg | reverse}}</div> // 差值表达式
<p :title="msg | toUp">鼠标长停</p> // 动态属性
data(){
return{
msg:'虾仁不眨眼'
}
},
filters:{
reverse (val) {
return val.split('').reverse().join('')
}
}
vue过滤器-传参和多过滤器
可同时使用多个过滤器, 或者给过滤器传参
过滤器传参
过滤器传参: vue变量 | 过滤器(实参)
<p>{{ msg | reverse }}</p>
filters:{
reverse (val) { // val 就是传过来的变量msg
return val.split('').reverse().join('')
}
}
多个过滤利使用
多个过滤器: vue变量 | 过滤器1 | 过滤器2
<p :title="msg | toUp | reverse('实参')"></p>
//全局过滤器
Vue.filter('reverse',(val)=>{return val.split('').reverse().join('') })
// 局部过滤器
filters:{
toUp (val) {
return val.toUpperCase()
}
}
vue计算属性
一个数据, 依赖另外一些数据计算而来的结果
计算属性名 和 data里的变量名 不能重复
// 语法
<div>{{ 计算属性名 }}</div>
data(){
return {
a:1,
b:2
}
},
computed: {
计算属性名 () {
return this.a + this.b
}
}
vue计算属性-缓存
计算属性是基于它们的依赖项的值结果进行缓存的,只要依赖的变量不变, 都直接从缓存里取结果
计算属性根据依赖变量结果缓存, 依赖变化重新计算结果存入缓存, 比普通方法性能更高
<div>{{ num }}</div>
<div>{{ num }}</div>
<div>{{ num }}</div>
<div>{{ sum() }}</div>
<div>{{ sum() }}</div>
<div>{{ sum() }}</div>
data() {
return {
a: 20,
b: 20
};
},
methods: {
sum() {
console.log('函数');
return this.a + this.b;
// 调用了3次 log了3次 是因为函数每调用一次就执行一次
}
},
computed: {
num() {
console.log('计算属性');
return this.a + this.b;
// 调用了三次 只log了1次 是因为第一次调用后存入缓存 只要依赖值不变 就读取缓存里的 ,依赖值发生改变就重新调用
}
}
// 计算属性优势:
// 带缓存
// 计算属性对应函数执行后, 会把return值缓存起来
// 依赖项不变, 多次调用都是从缓存取值
// 依赖项值-变化, 函数会"自动"重新执行-并缓存新的值
vue计算属性-完整写法
计算属性也是变量, 如果想要直接赋值, 需要使用完整写法
<input type="text" v-model="属性名">
computed: {
"属性名": {
// 给(属性名)赋值触发set方法
set(值){ // 双向绑定 input内容会传递到这
},
// 使用(属性名)的值触发get方法
get() {
return "值" // 这的值会传递到input
}
}
}
vue侦听器 -watch(属性检测)
浅侦听 可以侦听data/computed属性值改变 简单数据类型
watch: {
被侦听的属性名 (newVal, oldVal){
}
}
<input type="text" v-model="name">
data() {
return {
name: ''
};
},
watch: {
// newVal是当前输入框的最新值 oldVal是上一次输入框里的值
name(newVal, oldVal) { // 形参名随意 可只写1个 默认新值
console.log(newVal, oldVal);
}
}
vue侦听器-深度侦听和立即执行
侦听复杂类型, 或者立即执行侦听函数
watch: {
要侦听的属性名: {
immediate: true, // 立即执行
deep: true, // 深度侦听复杂类型内变化
handler (newVal, oldVal) {
}
}
}
<input v-model="user.name" type="text" />
<input v-model="user.age" type="text" />
watch: {
user: {
immediate: true, // 立即执行
deep: true, // 深度侦听复杂类型内变化
handler(newVal) {
console.log(newVal);
// 打印的是user里的对象
}
}
}
计算属性 和 属性检测(侦听器)
计算属性(computed) | 属性检测(watch) |
---|---|
首次运行 | 首次不运行 |
调用时需要在模板中渲染修改所依赖的元数据 | 调用时只需修改元数据 |
默认深度依赖 | 默认浅度观测 |
1、computed是计算属性; watch是监听一个值的变化执行对应的回调。
2、computed函数所依赖的属性不变的时候会调用缓存;watch每次监听的值发生变化时候都会触发。
3、computed必须有return ; watch可以没有。
4、computed的值受多个属性影响的时候;例如购物车商品结算; watch当一条数据影响多条数据的时候,例如搜索框。
vue组件
每个组件都是一个独立个体, 代码里体现为一个独立的.vue文件
口诀: 哪部分标签复用, 就把哪部分封装到组件内
(重要): 组件内template只能有一个根标签
(重要): 组件内data必须是一个函数, 独立作用域
(重要): 组件内style必须加 scoped 属性, 独立作用域
全局注册 main.js 内
// 1. 创建组件 - 文件名.vue 在components文件夹内
// 2. 引入组件
import Pannel(组件对象名) from './components/组件文件名'
// 3. 全局 - 注册组件 组件名当做标签使用
Vue.component("组件名(自定义)", 组件对象)
// 调用:在需要的位置添加组件标签 双标签和单标签均可
<div id="app">
<h3>案例:折叠面板</h3>
<Pannel></Pannel> // 组件
<Pannel /> // 组件
</div>
局部注册 App.vue 内
<div id="app">
<h3>案例:折叠面板</h3>
<Pannel></Pannel> // 组件名
<Pannel /> // 组件名
</div>
<script>
// 引入组件
import Pannel(组件对象名) from './components/Pannel-1.vue';
export default {
// 注册组件
components: {
// "组件名(自定义)", 组件对象
Pannel: Pannel
}
};
</script>
为了方便 组件名和组件对象名 一般用一个名字
vue组件通信
父: 使用其他组件的vue文件
子: 被引入的组件(嵌入)
// 组件通信的方式
1. 子父组件通信
2. 事件总线(EventBus)
3. 依赖注入 (数据流向不好追溯)
4. vuex (常用)
5. 浏览器缓存
vue组件通信_父向子-props
从父给子组件内传值
子组件结构
<div>
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
</div>
export default {
props: ['title', 'price', 'intro']
}
父传值方法
// 1.引入组件 2. 注册组件
// 3.组件标签 传值
<Product title="好吃的口水鸡" price="50" intro="开业大酬宾, 全场8折"></Product>
父向子-配合循环
// 子组件结构不变
// 父
<ProductF v-for="obj in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info" />
data() {
return {
list: [
{ id: 1, proname: '超级好吃的棒棒糖', proprice: 18.8, info: '开业大酬宾, 全场8折' },
{ id: 2, proname: '超级好吃的大鸡腿', proprice: 34.2, info: '好吃不腻, 快来买啊' },
{ id: 3, proname: '超级无敌的冰激凌', proprice: 14.2, info: '炎热的夏天, 来个冰激凌了' }
]
};
},
components: {
ProductF: ProductF
}
};
父向子 props 校验规则
props: {
arr: {
type: Array, // 必须是数组
required: true, // 必须传值
// 自定义校验规则
validator(value) {
// value 是从父传过来的数据
if (value.length >= 2 && value.length <= 5) {
return true; // 符合条件就返回true
} else {
console.error('数据源必须在2-5条');
return false; // 不符合条件就返回false不接收数据
}
}
}
}
如果父组件不传值 给一个默认值
default 默认值
list: {
type: Array,
default: () => []
}
vue组件通信_单向数据流
从父到子的数据流向,叫做单项数据流
props变量本身是只读,不能重新赋值
在vue中需要遵循单向数据流原则
1. 父组件的数据发生了改变,子组件会自动跟着变
2. 子组件不能直接修改父组件传递过来的props props是只读的
vue组件通信 - v-model 传值
// 父页面
<组件 v-model="变量" />
// 子页面
props: ['value'] // 用value 接收
// 子向父
this.$emit('inpue',值)
vue组件通信_子向父
- 父: @自定义事件名=“父methods函数”
- 子: this.$emit(“自定义事件名”, 传值) - 执行父methods里函数代码
// 父
<div id="app">
<Product v-for="(obj, index) in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info" :index="index" @kanyidao="fn" />
</div>
methods: {
// 自定义事件的执行函数 形参接收子组件传过来的实参
fn(i, price) {
if (this.list[i].proprice > 1) {
this.list[i].proprice = (this.list[i].proprice - price).toFixed(1);
}
}
}
// 子
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<button @click="btn">砍一刀</button>
</div>
export default {
props: ['index', 'title', 'price', 'intro'],
methods: {
btn() {
// 触发父的自定义事件 后面是传的参数 子传父
this.$emit('kanyidao', this.index, 1);
}
}
};
$event 接收子传过来的值 的 方法
// 父向子传值
<Product :price="obj.proprice" @fn='obj.proprice = $event'/> // 用 $event 可以接收子传来的值
// 子向父传值
this.$emit('fn',1)
在子组件调用父组件里的方法
this.$parent.方法名()
vue组件通信 - 依赖注入
父组件向子组件或子组件的子组件传值
// 父组件
export default {
provide: function () {
return {
参数名: 参数值
}
}
};
// 子组件 或 孙子组件...
export default {
inject: {
参数名: {
// 参数规则
type:[Number, String],
default: null
}
}
};
---------------------------------------------------
// 或许可以这样写 有待实验
export default {
inject: ['参数名']
};
vue组件通信-EventBus (跨组件通信)
src/EventBus/index.js - 创建空白vue对象 并导出
要接收值的组件(List.vue) eventBus.$on(‘自定义事件名’,函数体)
要传递值的组件(Product.vue) eventBus.$emit(‘自定义事件名’, 要传递的参数)
新建 src/EventBus/index.js- 定义事件总线bus对象
// index.js
import Vue from 'vue'
// 导出空白vue对象
export default new Vue()
要接收值的组件 list.vue (空白vue对象 - 监听和触发事件)
<template>
<!-- 要渲染在页面上的内容 -->
<ul class="my-product">
<li v-for="(item, index) in arr" :key="index">
<span>{{ item.proname }}</span>
<span>{{ item.proprice }}</span>
</li>
</ul>
</template>
<script>
// 引入空白对象(EventBus)
import eventBus from '../EventBus/index';
export default {
// arr 里的数据 通过父组件传递过来
props: ['arr'],
// 生命周期函数 自执行
created() {
// 接收方 监听 send 事件(自定义事件名) 函数参数通过传递值得组件传递过来 根据值给数据做出改变
eventBus.$on('send', (i, price) => {
if (this.arr[i].proprice > 1) {
this.arr[i].proprice = (this.arr[i].proprice - price).toFixed(1);
}
});
}
};
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
传递值的组件 (Product.vue)
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<button @click="btn">砍一刀</button>
</div>
</template>
<script>
// 引入空白对象(EventBus)
import eventBus from '../EventBus/index';
export default {
props: ['index', 'title', 'price', 'intro'],
methods: {
btn() {
// 跨组件方法 (自定义事件,要传递的参数)
eventBus.$emit('send', this.index, 1);
}
}
};
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
父组件
<template>
<div id="app">
<!-- 使用组件 传递值得组件 -->
<div style="float: left">
<Product
v-for="(obj, index) in list"
:key="obj.id"
:title="obj.proname"
:price="obj.proprice"
:intro="obj.info"
:index="index"
@kanyidao="fn"
/>
</div>
<!-- 组件 接收值的组件 将数据传输过去 -->
<div style="float: left">
<List :arr="list" />
</div>
</div>
</template>
<script>
// 引入组件
import Product from './components/ProductSub.vue';
import List from './components/List_.vue';
export default {
data() {
return {
// 数据
list: [
{ id: 1, proname: '超级好吃的棒棒糖', proprice: 18.8, info: '开业大酬宾, 全场8折' },
{ id: 2, proname: '超级好吃的大鸡腿', proprice: 34.2, info: '好吃不腻, 快来买啊' },
{ id: 3, proname: '超级无敌的冰激凌', proprice: 14.2, info: '炎热的夏天, 来个冰激凌了' }
]
};
},
// 注册组件
components: {
Product: Product,
List: List
}
};
</script>
<style lang="less"></style>
vue生命周期
组件从 创建 到 销毁 的整个过程就是生命周期
钩子函数
Vue 框架内置函数,随着组件的生命周期阶段,自动执行
作用: 特定的时间点,执行特定的操作
场景: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据
分类: 4大阶段8个方法
阶段 | 方法名 | 方法名 |
---|---|---|
初始化 | beforeCreate | created |
挂载 | beforeMount | mounted |
更新 | beforeUpdate | updated |
销毁 | beforeDestroy | destroyed |
初始化阶段
初始化前 beforeCreate
export default {
data() {
return {
msg: '虾仁不眨眼'
};
},
// 初始化之前 没有data和methods方法
beforeCreate() {
console.log(this.msg); // undefined
}
};
初始化后 created
export default {
data() {
return {
msg: '虾仁不眨眼'
};
},
// 初始化之后 才有data和methods方法
created() {
console.log(this.msg); // 虾仁不眨眼
}
};
含义讲解:
1.new Vue() – Vue实例化(组件也是一个小的Vue实例)
2.Init Events & Lifecycle – 初始化事件和生命周期函数
3.beforeCreate – 生命周期钩子函数被执行
4.Init injections&reactivity – Vue内部添加data和methods等
5.created – 生命周期钩子函数被执行
挂载阶段
挂载之前 beforeMount
<p id="myP">我是一个P标签</p>
export default {
// 挂载之前 没有真实DOM
beforeMount() {
console.log(document.querySelector('#myP'));
// null
}
};
挂载之后 mounted
<p id="myP">我是一个P标签</p>
export default {
// 挂载之后 才有了真实DOM
mounted() {
console.log(document.querySelector('#myP'));
// <p id="myP">我是一个P标签</p>
}
};
含义讲解:
1.这时候Vue开始编译模版,把编译好的模版,渲染好的DOM放在内存中
,此时并没有挂载到html页面中去。
2.beforeMount – 生命周期钩子函数被执行 此时模版编译好了,但是还未挂载到dom中,
3.将编译好的模版替换到浏览器页面中 真实DOM挂载完毕
4.mounted – 生命周期钩子函数被执行 为这时候才能拿到真实的dom,至此页面创建完成
更新阶段
更新之前 beforeUpdate
<ul>
<li v-for="(item, index) in arr" :key="index">{{ item }}</li>
</ul>
<button @click="arr.push(4)">点击</button>
data() {
return {
arr: [1, 2, 3]
};
},
// 更新之前 需要点击后触发 数据不发生改变不会触发更新函数
beforeUpdate() {
console.log(document.querySelectorAll('li')[3]);
// undefined 此时页面数据是旧的data数据是最新的 还未进行同步
},
更新之后 updated
<ul>
<li v-for="(item, index) in arr" :key="index">{{ item }}</li>
</ul>
<button @click="arr.push(4)">点击</button>
data() {
return {
arr: [1, 2, 3]
};
},
// 更新之后 需要点击后触发 数据不发生改变不会触发更新函数
updated() {
console.log(document.querySelectorAll('li')[3]); // <li>4</li> 这时候页面和data数据都同步完毕,都是最新的
}
含义讲解:
1.当data里数据改变, 触发更新
2.beforeUpdate – 生命周期钩子函数被执行 这时候页面中的数据还是旧的,此时data中的数据是最新的。页面还未和数据进行同步
3.更新中 这时候根据data最新的数据,重新渲染最新的dom结构
4.updated – 生命周期钩子函数被执行 这时候页面和data数据都同步完毕,都是最新的
5.当有data数据改变 – 重复这个循环
销毁阶段
销毁之前 beforeDestroy
销毁之后 destroyed
App.vue - 点击按钮让Life组件移除 ->Life组件进入销毁阶段 才会触发 这两个方法
// 父组件 点击销毁 子组件
<template>
<div>
<Life v-if="isShow" />
<button @click="isShow = false">销毁</button>
</div>
</template>
<script>
import Life from './components/Life_.vue';
export default {
data() {
return {
isShow: true
};
},
components: {
Life: Life
}
};
</script>
<style></style>
// 子组件
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
timer: ''
};
},
created() {
this.timer = setInterval(function () {
console.log('哈哈哈');
}, 1000);
},
// 销毁之前
beforeDestroy() {
clearInterval(this.timer);
},
// 销毁之后
destroyed() {
console.log('销毁之后');
}
};
</script>
<style></style>
含义讲解:
1. beforeDestroy 这时候通过某些恰当的时机,Vue实例进入到销毁阶段,这是销毁前。此时vue指令、过滤器、data数据和methods里的方法都可用,还未真正销毁。
2. 销毁中
3. destroyed 此时组件已经完全销毁,vue指令、过滤器、data数据和methods都不能用了,这个组件彻底凉了
axios 数据请求
是一个专门用于发送Ajax请求的库
特点:
* 支持客户端发送Ajax请求
* 支持服务端Node.js发送请求
* 支持Promise相关用法
* 支持请求和响应的拦截器功能
* 自动转换JSON数据
* axios 底层还是原生js实现, 内部通过Promise封装的
下包
npm install axios
导入局部
import axios from 'axios';
基本使用
axios({
method: '请求方式', // get post
url: '请求地址',
data: { // 拼接到请求体的参数, post请求的携带参数
xxx: xxx,
},
params: { // 拼接到请求行的参数, get请求的携带参数
xxx: xxx
}
}).then(res => {
console.log(res.data) // 后台返回的结果
}).catch(err => {
console.log(err) // 后台报错返回
})
axios-局部配置地址
// App.vue
import axios from 'axios'; // 引入包
// 配置地址
axios.defaults.baseURL = "http://123.57.109.30:3006"
axios-全局配置
main.js
// 1.全局绑定属性 (确保任意.vue文件可以都访问到这个axios方法)
import axios from 'axios'
// 2. 配置地址
axios.defaults.baseURL = "接口根地址"
// 3. axios方法添加到Vue的原型上
Vue.prototype.axios = axios
// 使用方法 this.axios
获取DOM
通过id 或 ref 获取dom (ref 相当于 id)
<h1 id="h" ref="myh">我是一个孤独可怜又能吃的h1</h1>
在 mounted 生命周期中获取
mounted() {
// 通过id 获取
console.log(document.querySelector('#h'));
// 通过 ref 获取
console.log(this.$refs.myh);
}
获取组件对象
通过给组件标签 起 ref 名 获取
可以在父组件调用子组件内的函数
获取组件对象获取的是子组件内的 export default { } 对象
// ----子组件
export default {
methods: {
fn(){ // 子组件内的fn() 函数
console.log('我是子组件内的方法');
}
}
};
// ----父组件
<More ref='more'></More> // 子组件标签
// 引入组件 注册组件
export default {
mounted(){
let moreobj = this.$refs.more // 获取组件对象
// 调用组件内的方法
moreobj.fn() // 打印:我是子组件内的方法
}
}
$nextTick使用
Vue更新DOM是异步的
点击count++, 马上通过"原生DOM"拿标签内容, 无法拿到新值
DOM更新完后才会触发$nextTick里的函数体
<p ref="p">{{ count }}</p>
<button @click="btn">+1</button>
data() {
return {
count: 0
};
},
methods: {
btn() {
this.count++;
// vue监测数据更新, 开启一个DOM更新队列(异步任务)
console.log(this.$refs.p.innerHTML); // 0
// 解决: this.$nextTick()
// 过程: DOM更新完会挨个触发$nextTick里的函数体
this.$nextTick(() => {
console.log(this.$refs.p.innerHTML); // 1
});
}
}
组件name属性使用
可以用组件的name属性值, 来注册组件名字
// 子组件 Com.vue
export default {
name: "ComNameHaHa" // 可以定义自己的名字
}
// 父组件
<ComNameHaHa></ComNameHaHa>
import Com from './components/Com'
components: {
[Com.name]: Com // 对象里的key是变量的话[]属性名表达式
// 相当于 "ComNameHaHa": Com
}
组件进阶
动态组件
多个组件使用同一个挂载点,并动态切换,这就是动态组件
挂载点 , 使用is属性来设置要显示哪个组件
<div>
<button @click="comName = 'UserName'"></button>
<button @click="comName = 'UserInfo'"></button>
<!--上面点击 给comName变量赋值对应组件名实现切换-->
<!--挂载点 is属性决定显示哪个组件 comName变量存放组件名-->
<component :is="comName"></component>
</div>
// 引入组件...此处省略
export default {
data() {
return {
comName: 'UserName'
};
},
components: {
UserName,
UserInfo
}
};
组件缓存
组件切换会导致组件被频繁销毁和重新创建, 性能不高
标签包裹 可以将组件缓存起来
<div style="border: 1px solid red;">
<!-- Vue内置keep-alive标签, 把包起来的组件缓存起来 -->
<keep-alive>
<component :is="comName"></component>
</keep-alive>
</div>
// 不会频繁创建和销毁而是会触发子组件的激活和非激活生命周期函数
// 当切换到该子组件的时候 会触发该子组件的激活函数
// 当切换到其他子组件隐藏了该子组件 会触发该子组件的非激活函数
keep-alive可以提高组件的性能, 内部包裹的标签不会被销毁和重新创建, 触发激活和非激活的生命周期方法
激活和非激活
被缓存的组件不再创建和销毁, 而是激活和非激活
activated - 激活
activated() {
console.log('这个组件激活了');
}
// 当切换到该子组件的时候 会触发该子组件的激活函数
deactivated - 失去激活状态
deactivated() {
console.log('这个组件没有激活');
}
// 当切换到其他子组件隐藏了该子组件 会触发该子组件的非激活函数
使用场景: 失去激活状态时清除定时器 等等…
组件缓存扩展
指定要缓存的组件 include 属性
<keep-alive include='组件的name名'>
<component :is="comName"></component>
</keep-alive>
// 该组件缓存 其他组件不缓存
指定不缓存的组件 exclude 属性
<keep-alive exclude='组件的name名'>
<component :is="comName"></component>
</keep-alive>
// 该组件不缓存 其他组件缓存
组件的name名
// 子组件
export default {
name: "Com" // 可以自定义自己的name名字
}
组件插槽
通过 slot 标签, 可以接收到写在组件标签内的内容
组件插槽能力, 允许开发者在封装组件时,把不确定的部分定义为插槽
语法口诀:
1. 组件内用<slot></slot>占位
2. 使用组件时<Pannel></Pannel>夹着的地方,传入内容替换slot
// 父组件 在组件标签内写的内容会 传入到子组件的slot标签处
<div id="app">
<h3>案例:折叠面板</h3>
<Pannel>
<p>寒雨连江夜入吴, </p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</Pannel>
<Pannel>
<img src="./assets/1.jpg" alt="" />
</Pannel>
</div>
// 子组件
// slot标签里写内容则为默认内容 会被父组件传过来的内容替换掉
<div v-show="isShow" class="container">
<slot></slot>
</div>
slot标签里写内容则为默认内容 会被父组件传过来的内容替换掉
具名插槽
当一个组件内有2处以上需要外部传入标签的地方
传入的标签可以分别派发给不同的slot位置
v-slot一般用跟template标签使用
语法:slot的name属性起插槽名, 使用组件时, template配合v-slot:插槽名传入具体标签 v-slot: 可简写为 #
// 父组件
<Pannel> // 组件标签
<template v-slot:title>
<h4>芙蓉楼送辛渐</h4>
</template>
<template v-slot:content>
<p>寒雨连江夜入吴, </p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</template>
</Pannel>
// 父组件的内容会根据 v-slot:插槽名 传到子组件里 slot 标签的对应的 name 名 处
// 子组件
<slot name="title"></slot>
<slot name="content"></slot>
作用域插槽
使用子组件内的 变量
语法:
子组件slot标签 :row='要绑定的变量'
父组件<template>标签 v-slot='自定义变量名'
用法:{{ 自定义变量名.row.xxx }}
// 子组件 User.vue
<div>
<!-- :row='list'将变量绑定在slot标签的row属性上 -->
<slot :row='list'></slot>
</div>
export default {
data(){
return{
list:{
name:'虾仁不眨眼',
age:18
}
};
// 父组件
<User> // 组件标签
<!-- v-slot='自定义变量名' 自定义变量名自动绑定子组件slot标签上所有属性和值 -->
<template v-slot='scope'>
<span>{{ scope.row.name }}</span>
<span>{{ scope.row.age }}</span>
</template>
</User>
具名插槽和作用域插槽同时使用
// 子
<slot name='body' :row='list'></slot>
// 父组件
<User> // 组件标签
<!-- #子slot标签的name名='自定义变量名' 自定义变量名自动绑定子组件slot标签上所有属性和值 -->
<template #body='scope'>
<span>{{ scope.row.name }}</span>
<span>{{ scope.row.age }}</span>
</template>
</User>
自定义指令 directives
获取标签, 扩展额外的功能
全局注册 main.js内
Vue.directive('自定义指令名', {
inserted(el) {
// el 是形参 获取的是用这个自定义指令的标签
console.log(el); // <input type="text" />
el.focus(); // 触发标签的事件方法 获取焦点事件
}
});
局部注册
export default {
directives: {
自定义指令名: {
inserted(el) {
// el 是形参 获取的是用这个自定义指令的标签
el.focus();
}
}
}
};
使用自定义指令
<!-- 使用自定义指令就会运行 自定义指令里的函数 -->
<div>
<input v-自定义指令名 type="text" />
</div>
自定义指令传值
v-自定义指令名=“要传的变量名”
页面一开始 触发自定义指令的 inserted 函数
传的值发生改变 触发的是自定义指令的 update 函数
<div>
<span v-clr="color" @click="color = 'blue'">改变颜色</span>
</div>
export default {
data() {
return {
color: 'red'
};
},
// 自定义指令
directives: {
clr: { // clr 自定义的指令名
// inserted 函数会在页面一开始执行一次
inserted(el,str) {
// 两个形参 第一个获取写自定义指令的标签
// 第二个获取 自定义指令传过来的值
el.style.color = str.value;
},
// 之后自定义指令传的值发生了更改 触发的是这个函数
update(el, str) {
console.log(str.value); // blue
el.style.color = str.value;
}
}
}
};
vue路由
是一种映射关系
vue中的路由:路径和组件的映射关系
为什么使用路由 单页面概念 优缺点
在一个页面里, 切换业务场景
优点:
整体不刷新页面,用户体验更好
数据传递容易, 开发效率高
缺点:
开发成本高(需要学习专门知识)
首次加载会比较慢一点。不利于seo
路由 - 组件分类
.vue文件分2类, 一个是页面组件, 一个是复用组件
src/views(或pages) 文件夹 和 src/components文件夹
- 页面组件 - 页面展示 - 配合路由用
- 复用组件 - 展示数据/常用于复用
vue-router使用
安装
yarn add vue-router@3.5.2
导入路由
import VueRouter from 'vue-router'
使用路由插件
// 在vue中,使用使用vue的插件,都需要调用Vue.use()
Vue.use(VueRouter)
创建路由规则数组
// 当点击a标签 <a href="#/find">发现音乐</a> 在规则数组里找对应的组件名 把他显示在 挂载点
// 引入组件 @代表src
import Find from '@/views/Find';
import My from '@/views/My';
import Part from '@/views/Part';
// 路由规则数组
const routes = [
{
path: "/find", // http://localhost:8888/#/find
component: Find // 对应引入的组件名
},
{
path: "/my",
component: My
},
{
path: "/part",
component: Part
}
]
创建路由对象 - 传入规则
const router = new VueRouter({
routes
})
关联到vue实例
new Vue({
router
})
挂载点 components 换成 router-view
<router-view></router-view>
下载路由模块, 编写对应规则注入到vue实例上, 使用router-view挂载点显示切换的路由
一切都围绕着hash值变化为准
如果是自定义搭建的项目 路由模式要更改
// router文件里index.js 内
// 路由模式 history 改成 hash
const router = new VueRouter({
mode: 'history', // 改成 hash
base: process.env.BASE_URL,
routes
});
路由 - 模式设置
router/index.js 见上面
hash路由例如: http://localhost:8080/#/home
history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹) 需要和后台商量好
vue路由 - 声明式导航
可用全局组件router-link来替代a标签
1. vue-router提供了一个全局组件 router-link
2. router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需写#)
3. router-link提供了声明式导航高亮的功能(自带类名)
类名:router-link-exact-active router-link-active
<router-link to="/find">发现音乐</router-link>
声明导航 - 类名区别
-
router-link-exact-active (精确匹配) url中hash值路径, 与href属性值完全相同, 设置此类名
-
router-link-active (模糊匹配) url中hash值, 包含href属性值这个路径
声明式导航 - 跳转传参(两种)
在跳转路由时, 可以给路由对应的组件内传值
/path?参数名=值 (传值)
对应页面组件接收传递过来的值
$route.query.参数名 (取值)
/path/值 – 需要路由对象提前配置 path: “/path/参数名”
对应页面组件接收传递过来的值
$route.params.参数名
// 导航跳转, 传值给组件
<router-link to="/part?name=小传">朋友-小传</router-link>
// 路由规则定义
{
path: "/part",
component: Part
}
// 组件接收值
<p>人名: {{ $route.query.name }} </p>
// 导航跳转, 传值给组件
<router-link to="/part/小智">朋友-小智</router-link>
// 路由规则定义
{
path: "/part/:username", // 有:的路径代表要接收具体的值
component: Part
}
// 组件接收值
<p>人名: {{ $route.params.username }} </p>
总结:
?key=value 用$route.query.key 取值
/值 提前在路由规则/path/:key 用$route.params.key 取值
返回上一页面
this.$router.back();
跳转页面
// 跳转到对应页面
this.router.replace('/login');
this.router.push('/login');
路由 - 重定向 redirect
匹配path后, 强制切换到目标path上
就是更改默认打开的组件页面
网页打开url默认hash值是/路径
redirect是设置要重定向到哪个路由路径
例如: 网页默认打开, 匹配路由"/", 强制切换到"/find"上
const routes = [
{
path: "/", // 默认hash值路径
redirect: "/find" // 重定向到/find
// 浏览器url中#后的路径被改变成/find-重新匹配数组规则
},
{
path: "/find",
component: Find
}
]
路由 - 404页面
如果路由hash值, 没有和数组里规则匹配 默认给一个404页面
import NotFound from '@/views/NotFound' // 引入404组件
// 路由规则
const routes = [
// ...省略了其他配置
// 404 规则 要写在所有规则最下面
{
path: '*', // 当找不到对应页面 就会打开 404 页面
component: NotFound // 404 页面组件
}
]
路由监听
监听当前页面 路由对象$route 的变化
就是当前页面 挂载点 的组件变化
// 路由规则里 给路由设置元信息
{
path: '/home',
component: Home,
meta: { // 元信息
// meta保存路由对象额外信息的
title: '首页' // title 是自定义的变量
}
},
{
path: '/search',
component: Search,
meta: { // 元信息
title: '搜索' // title 是自定义的变量
}
}
<router-view></router-view> // 挂载点
// 路由切换时-侦听$route对象改变,就是当前页面 挂载点 的组件变化
watch: {
$route() {
console.log(this.$route.meta.title;)
// 可以拿到设置的元信息里的变量值
},
},
vue路由 - 编程式导航
@click="$router.push(‘/login’) 可直接跳转
用JS代码来进行跳转
常用 name 来跳转
语法:
this.$router.push({
path: "路由路径", // 都去 router/index.js定义
name: "路由名"
})
// 路由数组规则里, 给路由起名字
const routes = [
{
path: '/find', // 路由路径
name: 'Find', // 路由名字 自定义
component: Find // 组件
}
]
<span @click="btn('/find', 'Find')">发现音乐</span>
export default {
methods: {
// 通过点击传实参过来 使用 path 或 name 跳转
btn(Path, Name){
this.$router.push({
// 方式1: path跳转
path: Path,
// 方式2: name跳转
name: Name
})
}
}
};
编程式导航 - 跳转传参
JS跳转路由, 传参
重要: path会自动忽略params
推荐: name+query方式传参
注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
语法 query / params 任选 一个
this.$router.push({
path: "路由路径" // 用这个跳转只能用 query 传参
name: "路由名", // 用name跳转 两种传参方式都可用
query: { // 方式1: query 传参
"参数名": 值
}
params: { // 方式2: params 传参
"参数名": 值
}
})
// 对应路由接收 $route.params.参数名 取值
// 对应路由接收 $route.query.参数名 取值
query 传的参数 会在URL地址后?拼接
params 传的参数 不会在URL地址上显示
跳转传参 props接收参数
// 路由配置
{
path: '/article/:articleId', // 参数名
name: 'article',
component: Article,
props: true // 开启 props 传参 接收参数
},
// 跳转依赖页面 声明式导航
<标签名 :to="`/article/${item.art_id}`"></标签名>
// 要跳转到的页面 props接收参数
props: {
articleId: {
type: [Number, String],
required: true
}
}
路由跳转并传参并打开新页面
// 点击事件把要传的参数传过来
detail(id){
let route2 = this.$router.resolve({
name:'ourserve', //这里是跳转到的页面的name 和目标页面的name一致
query:{
id:id //要传的参数
}
})
window.open(route2.href,'_blank') //打开新的页面
}
vue路由 - 路由嵌套
在现有的一级路由下, 再嵌套二级路由
children数组里编写路由信息对象
main.js– 继续配置2级路由
一级路由path从/开始定义
二级路由往后path直接写名字, 无需 / 开头
嵌套路由在上级路由的children数组里编写路由信息对象
<router-link to="/find/recommend">推荐</router-link>
<router-link to="/find/ranking">排行榜</router-link>
<router-link to="/find/songlist">歌单</router-link>
// 点击上面链接 在当前find页面内的挂载点区域 打开二级页面
<router-view></router-view> // 挂载点 当前 Find 页面内
const routes = [ // 路由规则
// ...省略其他
{
path: "/find",
name: "Find",
component: Find,
children: [ // 二级路由
{
path: "recommend", // /find/recommend
component: Recommend // 二级页面组件
},
{
path: "ranking",
component: Ranking
},
{
path: "songlist",
component: SongList
}
]
}
// ...省略其他
]
嵌套路由, 找准在哪个页面里写router-view挂载点和对应规则里写children
全局前置守卫
路由跳转之前, 先执行一次前置守卫函数, 判断是否可以正常跳转
// 语法: router.beforeEach((to, from, next)=>{//路由跳转"之前"先执行这里, 决定是否跳转})
// 参数1: 要跳转到的路由 (路由对象信息) 目标
// 参数2: 从哪里跳转的路由 (路由对象信息) 来源
// 参数3: 函数体 - next()才会让路由正常的跳转切换, next(false)在原地停留, next("强制修改到另一个路由路径上")
// 注意: 如果不调用next, 页面留在原地
main.js
import router from '@/router'
// 例子: 判断用户是否登录, 是否决定去"我的音乐"/my
const isLogin = false; // 模拟登录状态(未登录)
router.beforeEach((to, from, next) => {
if (to.path === "/my" && isLogin) {
next() // 正常放行
} else {
alert("请登录")
next(false) // 阻止路由跳转
// next('/part') 强制跳转到指定页面
}
})
next()放行, next(false)留在原地不跳转路由, next(path路径)强制换成对应path路径跳转