vue组件
组件是可复用的 Vue 实例, 封装标签, 样式和JS代码
组件化 :封装的思想,把页面上 可重用的部分
封装为 组件
,从而方便项目的 开发 和 维护
一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)
vue组件_基础使用
每个组件都是一个独立的个体, 代码里体现为一个独立的.vue文件
哪部分标签复用, 就把哪部分封装到组件内
(重要): 组件内template只能有一个根标签
组件内data必须是一个函数, 独立作用域
步骤:
- 创建组件 components/Pannel.vue
封装标签+样式+js - 组件都是独立的, 为了复用
<template>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="isShow = !isShow">
{{ isShow ? "收起" : "展开" }}
</span>
</div>
<div class="container" v-show="isShow">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
};
},
};
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
- 注册组件: 创建后需要注册后再使用
这就会有一个点击收起,点击展开的效果
全局 - 注册使用
全局入口在main.js, 在new Vue之上注册
语法:
import Vue from 'vue'
import 组件对象 from 'vue文件路径'
Vue.component("组件名", 组件对象)
main.js - 立即演示
// 目标: 全局注册 (一处定义到处使用)
// 1. 创建组件 - 文件名.vue
// 2. 引入组件
import Pannel from './components/Pannel'
// 3. 全局 - 注册组件
/*
语法:
Vue.component("组件名", 组件对象)
*/
Vue.component("PannelG", Pannel)
全局注册PannelG组件名后, 就可以当做标签在任意Vue文件中template里用
单双标签都可以或者小写加-形式, 运行后, 会把这个自定义标签当做组件解析, 使用组件里封装的标签替换到这个位置
局部 - 注册使用
语法:
import 组件对象 from 'vue文件路径'
export default {
components: {
"组件名": 组件对象
}
}
任意vue文件中中引入, 注册, 使用
<template>
<div id="app">
<h3>案例:折叠面板</h3>
<!-- 4. 组件名当做标签使用 -->
<!-- <组件名></组件名> -->
<PannelG></PannelG>
<PannelL></PannelL>
</div>
</template>
<script>
// 目标: 局部注册 (用的多)
// 1. 创建组件 - 文件名.vue
// 2. 引入组件
import Pannel from './components/Pannel_1'
export default {
// 3. 局部 - 注册组件
/*
语法:
components: {
"组件名": 组件对象
}
*/
components: {
PannelL: Pannel
}
}
</script>
组件使用总结:
- (创建)封装html+css+vue到独立的.vue文件中
- (引入注册)组件文件 => 得到组件配置对象
- (使用)当前页面当做标签使用
vue组件-scoped作用
解决多个组件样式名相同, 冲突问题
需求: div标签名选择器, 设置背景色
问题: 发现组件里的div和外面的div都生效了
解决: 给Pannel.vue组件里style标签上加scoped属性即可
<style scoped>
在style上加入scoped属性, 就会在此组件的标签上加上一个随机生成的data-v开头的属性
而且必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
style上加scoped, 组件内的样式只在当前vue组件生效
vue组件通信
因为每个组件的变量和值都是独立的
组件通信先暂时关注父传子, 子传父
父: 使用其他组件的vue文件
子: 被引入的组件(嵌入)
例如: App.vue(父) MyProduct.vue(子)
vue组件通信_父向子-props
步骤:
创建组件components/MyProduct.vue - 复制下面标签
组件内在props定义变量, 用于接收外部传入的值
App.vue中引入注册组件, 使用时, 传入具体数据给组件显示
components/MyProduct.vue - 准备标签
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
</div>
</template>
<script>
export default {
props: ['title', 'price', 'intro']
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
App.vue中使用并传入数据
<template>
<div>
<!--
目标: 父(App.vue) -> 子(MyProduct.vue) 分别传值进入
需求: 每次组件显示不同的数据信息
步骤(口诀):
1. 子组件 - props - 变量 (准备接收)
2. 父组件 - 传值进去
-->
<Product title="口水鸡" price="50" intro="开业大酬宾, 全场8折"></Product>
<Product title="可爱多" price="20" intro="全场1折"></Product>
<Product title="烤鸭" price="290" :intro="str"></Product>
</div>
</template>
<script>
// 1. 创建组件 (.vue文件)
// 2. 引入组件
import Product from './components/MyProduct'
export default {
data(){
return {
str: "快来啊, 好吃"
}
},
// 3. 注册组件
components: {
// Product: Product // key和value变量名同名 - 简写
Product
}
}
</script>
<style>
</style>
总结: 组件封装复用的标签和样式, 而具体数据要靠外面传入
vue组件通信_父向子-配合循环
把数据循环分别传入给组件内显示
<template>
<div>
<MyProduct v-for="obj in list" :key="obj.id"
:title="obj.proname"
:price="obj.proprice"
:intro="obj.info"
></MyProduct>
</div>
</template>
<script>
// 目标: 循环使用组件-分别传入数据
// 1. 创建组件
// 2. 引入组件
import MyProduct from './components/MyProduct'
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: " 来买",
},
],
};
},
// 3. 注册组件
components: {
// MyProduct: MyProduct
MyProduct
}
};
</script>
<style>
</style>
单向数据流
从父到子的数据流向,叫单向数据流
子组件修改,不通知父级,造成数据不一致性
如果第一个MyProduct.vue内自己修改商品价格为5.5, 但是App.vue里原来还记着18.8 - 数据 不一致了。所以: Vue规定props里的变量, 本身是只读的
vue组件通信_子向父
从子组件把值传出来给外面使用。
语法:
- 父: @自定义事件名=”父methods函数”
- 子: this.$emit(“自定义事件名”, 传值) - 执行父methods里函数代码
/image-20210217102551882.png)
示例代码
components/MyProduct_sub.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<button @click="subFn">砍1元</button>
</div>
</template>
<script>
import eventBus from '../EventBus'
export default {
props: ['index', 'title', 'price', 'intro'],
methods: {
subFn(){
this.$emit('subprice', this.index, 1) // 子向父
eventBus.$emit("send", this.index, 1) // 跨组件
}
}
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
App.vue
<template>
<div>
<!-- 目标: 子传父 -->
<!-- 1. 父组件, @自定义事件名="父methods函数" -->
<MyProduct v-for="(obj, ind) in list" :key="obj.id"
:title="obj.proname"
:price="obj.proprice"
:intro="obj.info"
:index="ind"
@subprice="fn"
></MyProduct>
</div>
</template>
<script>
import MyProduct from './components/MyProduct_sub'
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: {
MyProduct
},
methods: {
fn(inde, price){
// 逻辑代码
this.list[index].proprice > 1 && (this.list[index].proprice = (this.list[index].proprice - price).toFixed(2))
}
}
};
</script>
<style>
</style>
总结: 父自定义事件和方法, 等待子组件触发事件给方法传值
vue组件通信-EventBus
常用于跨组件通信时使用
两个组件的关系非常的复杂,通过父子组件通讯是非常麻烦的。这时候可以使用通用的组件通讯方案:事件总线
核心语法
EventBus/index.js- 定义事件总线bus对象
import Vue from 'vue'
// 导出空白vue对象
export default new Vue()
List.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>
export default {
props: ['arr'],
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
components/MyProduct_sub.vue(带学生主要写触发eventBus身上事件)
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<button @click="subFn">砍1元</button>
</div>
</template>
<script>
import eventBus from '../EventBus'
export default {
props: ['index', 'title', 'price', 'intro'],
methods: {
subFn(){
this.$emit('subprice', this.index, 1) // 子向父
eventBus.$emit("send", this.index, 1) // 跨组件
}
}
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
List.vue正确代码(EventBus接收方)
<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>
// 目标: 跨组件传值
// 1. 引入空白vue对象(EventBus)
// 2. 接收方 - $on监听事件
import eventBus from "../EventBus";
export default {
props: ["arr"],
// 3. 组件创建完毕, 监听send事件
created() {
eventBus.$on("send", (index, price) => {
this.arr[index].proprice > 1 &&
(this.arr[index].proprice = (this.arr[index].proprice - price).toFixed(2));
});
},
};
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
空的Vue对象, 只负责$on注册事件, $emit触发事件, 一定要确保$on先执行
一些问题
Vue组件如何进行传值的
父向子 -> props定义变量 -> 父在使用组件用属性给props变量传值
子向父 -> $emit触发父的事件 -> 父在使用组件用@自定义事件名=父的方法 (子把值带出来)
Vue 组件 data 为什么必须是函数
每个组件都是 Vue 的实例, 为了独立作用域, 不让变量污染别人的变量
有的数组方法不导致v-for更新页面, 如何处理?
- 拿返回的新数组, 直接替换旧数组
- this.$set()方法更新某个值
v-for更新时, 是如何操作DOM的?
循环出新的虚拟DOM结构, 和旧的虚拟DOM结构对比, 尝试复用标签就地更新内容
虚拟DOM是什么?
本质就是一个JS对象, 保存DOM关键信息
虚拟DOM好处?
提高DOM更新的性能, 不频繁操作真实DOM, 在内存中找到变化部分, 再更新真实DOM(打补丁)
虚拟DOM比较内容?
子元素或者内容改变会分diff哪2种情况比较?
无key, 就地更新
有key, 按照key比较
key值要求是?
唯一不重复的字符串或者数值
- key应该怎么用?
有id用id, 无id用索引
- key的好处?
可以配合虚拟DOM提高更新的性能
如何给标签class属性动态赋值
class=“{类名: 布尔值}”, true使用, false不用
给style赋值和class区别是?
- :class=”{类名: 布尔值}”, true使用, false不用
- :style=”{css属性名: 值}”
vue生命周期
一组件从 创建 到 销毁 的整个过程就是生命周期
钩子函数
Vue 框架内置函数,随着组件的生命周期阶段,自动执行
4大阶段8个方法
- 初始化
- 挂载
- 更新
- 销毁
阶段 | 方法名 | 方法名 |
---|---|---|
初始化 | beforeCreate | created |
挂载 | beforeMount | mounted |
更新 | beforeUpdate | updated |
销毁 | beforeDestroy | destroyed |
初始化阶段
含义讲解:
new Vue() – Vue实例化(组件也是一个小的Vue实例)
Init Events & Lifecycle – 初始化事件和生命周期函数
beforeCreate – 生命周期钩子函数被执行
Init injections&reactivity – Vue内部添加data和methods等
created – 生命周期钩子函数被执行, 实例创建
接下来是编译模板阶段 –开始分析
Has el option? – 是否有el选项 – 检查要挂到哪里
没有. 调用$mount()方法
有, 继续检查template选项
挂载阶段
含义讲解:
template选项检查
有 - 编译template返回render渲染函数
无 – 编译el选项对应标签作为template(要渲染的模板)
虚拟DOM挂载成真实DOM之前
beforeMount – 生命周期钩子函数被执行
Create … – 把虚拟DOM和渲染的数据一并挂到真实DOM上
真实DOM挂载完毕
mounted – 生命周期钩子函数被执行
代码示例
<template>
<div>
<p>学习生命周期 - 看控制台打印</p>
<p id="myP">{{ msg }}</p>
</div>
</template>
<script>
export default {
// ...省略其他代码
// 二. 挂载
// 真实DOM挂载之前
// 场景: 预处理data, 不会触发updated钩子函数
beforeMount(){
console.log("beforeMount -- 执行");
console.log(document.getElementById("myP")); // null
this.msg = "重新值"
},
// 真实DOM挂载以后
// 场景: 挂载后真实DOM
mounted(){
console.log("mounted -- 执行");
console.log(document.getElementById("myP")); // p
}
}
</script>
更新阶段
含义讲解:
当data里数据改变, 更新DOM之前
beforeUpdate – 生命周期钩子函数被执行
Virtual DOM…… – 虚拟DOM重新渲染, 打补丁到真实DOM
updated – 生命周期钩子函数被执行
当有data数据改变 – 重复这个循环
销毁阶段
含义讲解:
当$destroy()被调用 – 比如组件DOM被移除(例v-if)
beforeDestroy – 生命周期钩子函数被执行
拆卸数据监视器、子组件和事件侦听器
实例销毁后, 最后触发一个钩子函数
destroyed – 生命周期钩子函数被执行
代码示例
<script>
export default {
// ...省略其他代码
// 四. 销毁
// 前提: v-if="false" 销毁Vue实例
// 场景: 移除全局事件, 移除当前组件, 计时器, 定时器, eventBus移除事件$off方法
beforeDestroy(){
// console.log('beforeDestroy -- 执行');
clearInterval(this.timer)
},
destroyed(){
// console.log("destroyed -- 执行");
}
}
</script>
App.vue - 点击按钮让Life组件从DOM上移除 -> 导致Life组件进入销毁阶段
<Life v-if="show"></Life>
<button @click="show = false">销毁组件</button>
<script>
data(){
return {
show: true
}
},
</script>
axios
原生JS写AJAX
<script>
var xhr = new XMLHttpRequest;
xhr.open('GET','http://www.xxxxx:xxxx')
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState===4&&xhr.status===200){
console.log(xhr.responseText)
}
}
</script>
特点
- 支持客户端发送Ajax请求
- 支持服务端Node.js发送请求
- 支持Promise相关用法
- 支持请求和响应的拦截器功能
- 自动转换JSON数据
- axios 底层还是原生js实现, 内部通过Promise封装的
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基本使用-获取数据
代码示例
点击调用后台接口, 拿到所有数据 – 打印到控制台
<template>
<div>
<p>1. 获取所有图书信息</p>
<button @click="getAllFn">点击-查看控制台</button>
</div>
</template>
<script>
// 目标1: 获取所有图书信息
// 1. 下载axios
// 2. 引入axios
// 3. 发起axios请求
import axios from "axios";
export default {
methods: {
getAllFn() {
axios({
url: "http://123.57.109.30:3006/api/getbooks",
method: "GET", // 默认就是GET方式请求, 可以省略不写
}).then((res) => {
console.log(res);
});
// axios()-原地得到Promise对象
},
}
};
</script>
axios基本使用-传参
点击调用后台接口, 查询用户想要的书籍信息 – 打印到控制台
代码示例
<template>
<div>
<p>2. 查询某本书籍信息</p>
<input type="text" placeholder="请输入要查询 的书名" v-model="bName" />
<button @click="findFn">查询</button>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
bName: ""
};
},
methods: {
// ...省略了查询所有的代码
findFn() {
axios({
url: "/api/getbooks",
method: "GET",
params: { // 都会axios最终拼接到url?后面
bookname: this.bName
}
}).then(res => {
console.log(res);
})
}
},
};
</script>
axios基本使用-发布书籍
点击新增按钮, 把用户输入的书籍信息, 传递给后台 – 把结果打印在控制台
代码示例
<template>
<div>
<p>3. 新增图书信息</p>
<div>
<input type="text" placeholder="书名" v-model="bookObj.bookname">
</div>
<div>
<input type="text" placeholder="作者" v-model="bookObj.author">
</div>
<div>
<input type="text" placeholder="出版社" v-model="bookObj.publisher">
</div>
<button @click="sendFn">发布</button>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
bName: "",
bookObj: { // 参数名提前和后台的参数名对上-发送请求就不用再次对接了
bookname: "",
author: "",
publisher: ""
}
};
},
methods: {
// ...省略了其他代码
sendFn(){
axios({
url: "/api/addbook",
method: "POST",
data: {
appkey: "7250d3eb-18e1-41bc-8bb2-11483665535a",
...this.bookObj
// 等同于下面
// bookname: this.bookObj.bookname,
// author: this.bookObj.author,
// publisher: this.bookObj.publisher
}
})
}
},
};
</script>
axios基本使用-全局配置
避免前缀基地址, 暴露在逻辑页面里, 统一设置
axios.defaults.baseURL = "http://123.57.109.30:3006"
// 所有请求的url前置可以去掉, 请求时, axios会自动拼接baseURL的地址在前面
getAllFn() {
axios({
url: "/api/getbooks",
method: "GET", // 默认就是GET方式请求, 可以省略不写
}).then((res) => {
console.log(res);
});
// axios()-原地得到Promise对象
},
$nextTick和$refs
$refs-获取DOM
export default {
mounted(){
console.log(document.getElementById("h")); // h1
console.log(this.$refs.myH); // h1
}
}
$refs-获取组件对象
代码示例
<template>
<div>
<p>1. 获取原生DOM元素</p>
<h1 id="h" ref="myH">我是h1</h1>
<p>2. 获取组件对象 - 可调用组件内一切</p>
<Demo ref="de"></Demo>
</div>
</template>
<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
import Demo from './Child/Demo'
export default {
mounted(){
console.log(document.getElementById("h")); // h1
console.log(this.$refs.myH); // h1
let demoObj = this.$refs.de;
demoObj.fn()
},
components: {
Demo
}
}
</script>
$nextTick使用
Vue更新DOM是异步的,所以更新DOM后无法立即拿出
<template>
<div>
<p>1. 获取原生DOM元素</p>
<h1 id="h" ref="myH">h1</h1>
<p>2. 获取组件对象 - 可调用组件内一切</p>
<Demo ref="de"></Demo>
<p>3. vue更新DOM是异步的</p>
<p ref="myP">{{ count }}</p>
<button @click="btn">点击count+1, 马上提取p标签内容</button>
</div>
</template>
<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
import Demo from './Child/Demo'
export default {
mounted(){
console.log(document.getElementById("h")); // h1
console.log(this.$refs.myH); // h1
let demoObj = this.$refs.de;
demoObj.fn()
},
components: {
Demo
},
data(){
return {
count: 0
}
},
methods: {
btn(){
this.count++; // vue监测数据更新, 开启一个DOM更新队列(异步任务)
console.log(this.$refs.myP.innerHTML); // 0
// 原因: Vue更新DOM异步
// 解决: this.$nextTick()
// 过程: DOM更新完会挨个触发$nextTick里的函数体
this.$nextTick(() => {
console.log(this.$refs.myP.innerHTML); // 1
})
}
}
}
</script>
组件name属性使用
可以用组件的name属性值, 来注册组件名字。我们封装的组件-可以自己定义name属性组件名-让使用者有个统一的前缀风格。
components/Com.vue
<template>
<div>
<p>我是一个Com组件</p>
</div>
</template>
<script>
export default {
name: "ComNameHaHa" // 注册时可以定义自己的名字
}
</script>
App.vue - 注册和使用
<template>
<div>
<h1>1. 生命周期</h1>
<Life v-if="show"></Life>
<button @click="show = false">销毁组件</button>
<hr>
<h1>2. axios使用</h1>
<UseAxios></UseAxios>
<hr>
<h1>3. $refs的使用</h1>
<More></More>
<hr>
<h1>4. $nextTick使用场景</h1>
<Tick></Tick>
<hr>
<h1>5. 组件对象里name属性</h1>
<ComNameHaHa></ComNameHaHa>
</div>
</template>
<script>
import Life from './components/Life'
import UseAxios from './components/UseAxios'
import More from './components/More'
import Tick from './components/Tick'
import Com from './components/Com'
export default {
data(){
return {
show: true
}
},
components: {
Life,
UseAxios,
More,
Tick,
[Com.name]: Com // 对象里的key是变量的话[]属性名表达式
// "ComNameHaHa": Com
}
}
</script>
一些问题
Vue 的 nextTick 的原理是什么?
为什么需要 nextTick ,Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改–刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)这个 api 了。
理解原理前的准备 首先需要知道事件循环中宏任务和微任务这两个概念,常见的宏任务有 script, setTimeout, setInterval, setImmediate, I/O, UI rendering 常见的微任务有 process.nextTick(Nodejs),Promise.then(), MutationObserver;
理解 nextTick 的原理正是 vue 通过异步队列控制 DOM 更新和 nextTick 回调函数先后执行的方式。如果大家看过这部分的源码,会发现其中做了很多 isNative()的判断,因为这里还存在兼容性优雅降级的问题。可见 Vue 开发团队的深思熟虑,对性能的良苦用心。
vue生命周期总共分为几个阶段?
1)beforeCreate
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
2)created
在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer), 属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
3)beforeMount
在挂载开始之前被调用:相关的 render 函数首次被调用。
4)mounted
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
5)beforeUpdate
数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。
6)updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
7)activated
keep-alive 组件激活时调用。该钩子在服务器端渲染期间不被调用。
8)deactivated
keep-alive 组件停用时调用。该钩子在服务器端渲染期间不被调用。
9)beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。
10)destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
11)errorCaptured(2.5.0+ 新增)
当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。
动态组件
多个组件使用同一个挂载点,并动态切换,这就是动态组件
如图实现此功能
代码示例
<template>
<div>
<button @click="comName = 'UserName'">账号密码填写</button>
<button @click="comName = 'UserInfo'">个人信息填写</button>
<p>下面显示注册组件-动态切换:</p>
<div style="border: 1px solid red;">
<component :is="comName"></component>
</div>
</div>
</template>
<script>
// 目标: 动态组件 - 切换组件显示
// 场景: 同一个挂载点要切换 不同组件 显示
// 1. 创建要被切换的组件 - 标签+样式
// 2. 引入到要展示的vue文件内, 注册
// 3. 变量-承载要显示的组件名
// 4. 设置挂载点<component :is="变量"></component>
// 5. 点击按钮-切换comName的值为要显示的组件名
import UserName from '../components/01/UserName'
import UserInfo from '../components/01/UserInfo'
export default {
data(){
return {
comName: "UserName"
}
},
components: {
UserName,
UserInfo
}
}
</script>
vue内置component组件, 配合is属性, 设置要显示的组件名字
组件缓存
组件切换会导致组件被频繁销毁和重新创建, 性能不高,使用Vue内置的keep-alive组件, 可以让包裹的组件保存在内存中不被销毁
语法:
Vue内置的keep-alive组件 包起来要频繁切换的组件
<div style="border: 1px solid red;">
<!-- Vue内置keep-alive组件, 把包起来的组件缓存起来 -->
<keep-alive>
<component :is="comName"></component>
</keep-alive>
</div>
补充生命周期:
- activated - 激活
- deactivated - 失去激活状态
组件插槽
vue提供组件插槽能力, 允许开发者在封装组件时,把不确定的部分定义为插槽
语法
- 组件内用
占位 - 使用组件时
夹着的地方, 传入标签替换slot
组件内容分发技术, slot占位, 使用组件时传入替换slot位置的标签
如果外面不给传, 想给个默认显示内容
<slot>默认内容</slot>
自定义指令
除了核心功能默认内置的指令 (v-model
和 v-show
),Vue 也允许注册自定义指令。 v-xxx
,html+css的复用的主要形式是组件。你需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令
注册
局部注册代码案例
<template>
<div>
<!-- <input type="text" v-gfocus> -->
<input type="text" v-focus>
</div>
</template>
<script>
// 目标: 创建 "自定义指令", 让输入框自动聚焦
// 1. 创建自定义指令
// 全局 / 局部
// 2. 在标签上使用自定义指令 v-指令名
// 注意:
// inserted方法 - 指令所在标签, 被插入到网页上触发(一次)
// update方法 - 指令对应数据/标签更新时, 此方法执行
export default {
data(){
return {
colorStr: 'red'
}
},
directives: {
focus: {
inserted(el){
el.focus()
}
}
}
}
</script>
<style>
</style>
全局注册代码案例
// 全局指令 - 到处"直接"使用
Vue.directive("gfocus", {
inserted(el) {
el.focus() // 触发标签的事件方法
}
})
vue路由
vue-router使用
vue-router模块包,它和 Vue.js 深度集成,可以定义 - 视图表(映射规则)。
- 安装
npm install vue-router
- 导入路由
import VueRouter from 'vue-router'
- 使用路由插件
// 在vue中,使用使用vue的插件,都需要调用Vue.use()
Vue.use(VueRouter)
- 创建路由规则数组
const routes = [
{
path: "/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>
声明式导航
可用全局组件router-link来替代a标签
- vue-router提供了一个全局组件 router-link
- router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
- router-link提供了声明式导航高亮的功能(自带类名)
代码实例
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/part">朋友</router-link>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
/* 省略了 其他样式 */
.footer_wrap .router-link-active{
color: white;
background: black;
}
</style>
链接导航, 用router-link配合to, 实现点击切换路由
跳转传参
在跳转路由时, 可以给路由对应的组件内传值
在router-link上的to属性传值, 语法格式如下
/path?参数名=值
/path/值 – 需要路由对象提前配置 path: “/path/参数名”
对应页面组件接收传递过来的值
$route.query.参数名
$route.params.参数名
?key=value 用$route.query.key 取值
/值 提前在路由规则/path/:key 用$route.params.key 取值
重定向和模式
重定向
目标: 匹配path后, 强制切换到目标path上
- 网页打开url默认hash值是/路径
- redirect是设置要重定向到哪个路由路径
例如: 网页默认打开, 匹配路由”/“, 强制切换到”/find”上
const routes = [
{
path: "/", // 默认hash值路径
redirect: "/find" // 重定向到/find
// 浏览器url中#后的路径被改变成/find-重新匹配数组规则
}
]
总结: 强制重定向后, 还会重新来数组里匹配一次规则
404页面
目标: 如果路由hash值, 没有和数组里规则匹配
默认给一个404页面
语法: 路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面
创建NotFound页面
<template> <img src="../assets/404.png" alt=""> </template> <script> export default { } </script> <style scoped> img{ width: 100%; } </style>
在main.js - 修改路由配置
import NotFound from '@/views/NotFound' const routes = [ // ...省略了其他配置 // 404在最后(规则是从前往后逐个比较path) { path: "*", component: NotFound } ]
总结: 如果路由未命中任何规则, 给出一个兜底的404页面
模式设置
目标: 修改路由在地址栏的模式
hash路由例如: http://localhost:8080/#/home
history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)
router/index.js
const router = new VueRouter({
routes,
mode: "history" // 打包上线后需要后台支持, 模式是hash
})
编程式导航
用JS代码跳转, 声明式导航用a标签
语法:
this.$router.push({
path: "路由路径", // 都去 router/index.js定义
name: "路由名"
})
- main.js - 路由数组里, 给路由起名字
{
path: "/find",
name: "Find",
component: Find
},
{
path: "/my",
name: "My",
component: My
},
{
path: "/part",
name: "Part",
component: Part
},
- App.vue - 换成span 配合js的编程式导航跳转
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="btn('/part', 'Part')">朋友</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - js方式跳转路由
// 语法:
// this.$router.push({path: "路由路径"})
// this.$router.push({name: "路由名"})
// 注意:
// 虽然用name跳转, 但是url的hash值还是切换path路径值
// 场景:
// 方便修改: name路由名(在页面上看不见随便定义)
// path可以在url的hash值看到(尽量符合组内规范)
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
}
}
};
</script>
跳转传参
目标: JS跳转路由, 传参
语法 query / params 任选 一个
this.$router.push({
path: "路由路径"
name: "路由名",
query: {
"参数名": 值
}
params: {
"参数名": 值
}
})
// 对应路由接收 $route.params.参数名 取值
// 对应路由接收 $route.query.参数名 取值
使用path会自动忽略params
App.vue
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="oneBtn">朋友-小传</span>
<span @click="twoBtn">朋友-小智</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名
// 方式2:
// query => $route.query.参数名
// 重要: path会自动忽略params
// 推荐: name+query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
},
oneBtn(){
this.$router.push({
name: 'Part',
params: {
username: '小马'
}
})
},
twoBtn(){
this.$router.push({
name: 'Part',
query: {
name: '小胡'
}
})
}
}
};
</script>
总结: 传参2种方式
query方式
params方式
路由嵌套
目标: 在现有的一级路由下, 再嵌套二级路由
router-view嵌套架构图
创建需要用的所有组件
src/views/Find.vue – 发现音乐页
src/views/My.vue – 我的音乐页
src/views/Second/Recommend.vue – 发现音乐页 / 推荐页面
src/views/Second/Ranking.vue – 发现音乐页 / 排行榜页面
src/views/Second/SongList.vue – 发现音乐页 / 歌单页面
main.js– 继续配置2级路由
一级路由path从/开始定义
二级路由往后path直接写名字, 无需/开头
嵌套路由在上级路由的children数组里编写路由信息对象
说明:
App.vue的router-view负责发现音乐和我的音乐页面, 切换
Find.vue的的router-view负责发现音乐下的, 三个页面, 切换
/image-20210116162013983.png)
- 配置二级导航和样式在Find.vue中
<template>
<div>
<!-- <p>推荐</p>
<p>排行榜</p>
<p>歌单</p> -->
<div class="nav_main">
<router-link to="/find/recommend">推荐</router-link>
<router-link to="/find/ranking">排行榜</router-link>
<router-link to="/find/songlist">歌单</router-link>
</div>
<div style="1px solid red;">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.nav_main {
background-color: red;
color: white;
padding: 10px 0;
}
.nav_main a {
text-align: center;
text-decoration: none;
color: white;
font-size: 12px;
margin: 7px 17px 0;
padding: 0px 15px 2px 15px;
height: 20px;
display: inline-block;
line-height: 20px;
border-radius: 20px;
}
.nav_main a:hover {
background-color: brown;
}
.nav_main .router-link-active{
background-color: brown;
}
</style>
- 配置路由规则-二级路由展示
const routes = [
// ...省略其他
{
path: "/find",
name: "Find",
component: Find,
children: [
{
path: "recommend",
component: Recommend
},
{
path: "ranking",
component: Ranking
},
{
path: "songlist",
component: SongList
}
]
}
// ...省略其他
]
- 说明:
App.vue, 外层的router-view负责发现音乐和我的音乐页面切换
Find.vue 内层的router-view负责发现音乐下的子tab对应的组件切换
- 运行 - 点击导航观察嵌套路由在哪里展示
嵌套路由, 找准在哪个页面里写router-view和对应规则里写children