Vue组件高级
watch侦听器
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。例如,监视用户名的变化并发
起请求,判断用户名是否可用。
语法
<script>
export default{
data(){
return {username:''}
},
watch:{
//监听username值的变化
username(newVal,oldVal){
console.log(newVal,oldval)
}
},
}
</script>
检测用户名是否可用
import axios from 'axios'
export default{
data(){
return {username:''}
},
watch:{
async username(newVal,oldVal){
const {data:res} = await axios.get(url/${newVal})
console.log(res)
}
}
}
immediate选项
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使
用 immediate 选项。实例代码如下:
import axios from 'axios'
export default{
data(){
return {username:''}
},
watch:{
async username(newVal,oldVal){
const {data:res} = await axios.get(url/${newVal})
console.log(res)
}
},
//表示组件加载完毕后立即调用一次当前的watch侦听器
immediate:true
}
deep选项
当 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项,
代码示例如下:
import axios from 'axios'
export default{
data(){
return {info:{
username:''
}}
},
watch:{
async username(newVal,oldVal){
const {data:res} = await axios.get(url/${newVal})
console.log(res)
}
},
//需要使用deep选项,否则username的值的变化无法被监听到
deep:true
}
监听对象单个属性的变化
import axios from 'axios'
export default{
data(){
return {info:{
username:''
}}
},
watch:{
'info.username':{
async username(newVal,oldVal){
const {data:res} = await axios.get(url/${newVal})
console.log(res)
}
},
}
}
计算属性 vs 侦听器
计算属性和侦听器侧重的应用场景不同:
计算属性侧重于监听多个值的变化,最终计算并返回一个新值
侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值
组件的生命周期
组件的生命周期指的是:组件从创建 -> 运行(渲染) -> 销毁的整个过程,强调的是一个时间段。
开始 ->import导入组件->components注册组件->以标签形式使用组件->在内存中创建组件的实例对象->把创建的组件实例渲染到页面上->组件切换时销毁需要被隐藏的组件->结束
vue 框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。例如:
- 当组件在内存中被创建完毕之后,会自动调用 created 函数
- 当组件被成功的渲染到页面上之后,会自动调用 mounted 函数
- 当组件被销毁完毕之后,会自动调用 unmounted 函数
如何监听组件的更新
当组件的 data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证 View 视图展示的数据和
Model 数据源保持一致。
当组件被重新渲染完毕之后,会自动调用 updated 生命周期函数。
组建中主要的生命周期函数
生命周期函数 | 执行时机 | 所属阶段 | 执行次数 | 应用场景 |
---|---|---|---|---|
created | 组件在内存中创建完毕后 | 创建阶段 | 唯一1次 | 发 ajax 请求初始数据 |
mounted | 组件初次在页面中渲染完毕后 | 创建阶段 | 唯一1次 | 操作 DOM 元素 |
updated | 组件在页面中被重新渲染完毕后 | 运行阶段 | 0 或 多次 | - |
unmounted | 组件被销毁后(页面和内存) | 销毁阶段 | 唯一1次 | - |
组件中全部的生命周期函数
生命周期函数 | 执行时机 | 所属阶段 | 执行次数 | 应用场景 |
---|---|---|---|---|
beforeCreate | 在内存中开始创建组件之前 | 创建阶段 | 唯一1次 | |
created | 组件在内存中创建完毕后 | 创建阶段 | 唯一1次 | 发 ajax 请求初始数据 |
beforeMount | 在把组件初次渲染到页面之前 | 创建阶段 | 唯一1次 | |
mounted | 组件初次在页面中渲染完毕后 | 创建阶段 | 唯一1次 | 操作 DOM 元素 |
beforeUpdate | 在组件被重新渲染之前 | 运行阶段 | 0 或 多次 | |
updated | 组件在页面中被重新渲染完毕后 | 运行阶段 | 0 或 多次 | - |
beforeUnmount | 在组件被销毁之前 | 销毁阶段 | 唯一1次 | |
unmounted | 组件被销毁后(页面和内存) | 销毁阶段 | 唯一1次 | - |
组件之间的数据共享
组件之间的关系
在项目开发中,组件之间的关系分为如下 3 种:
- 父子关系
- 兄弟关系
- 后代关系
父子组件之间的数据共享
父子组件之间的数据分享又分为:
- 父 -> 子共享数据
- 子 -> 父共享数据
- 父 -> 子双向数据同步
父组件向子组件共享数据
父组件通过 v-bind 属性绑定向子组件共享数据。同时,子组件需要使用 props 接收数据。示例代码如下:
父组件
<my-test-1 :msg='message' :user='userinfo'></my-test-1>
data(){
return {
message:'hello vue',
userinfo:{name:zs,age,20}
}
}
子组件
<template>
<h3>
测试父子传值
</h3>
<p>
{{msg}}
</p>
<p>
{{user}}
</p>
</template>
<script>
export default{
props:['msg','user'],
}
</script>
子组件向父组件共享数据
子组件
<script>
export default{
emits:['n1change'],//声明自定义事件
data(){return {n1:0}},
methods:{
addN1(){
this.n1++
this.$emit('n1change',this.n1)
}
}
}
</script>
父组件
<my-test-1 @n1change='getn1'></my-test-1>
export default{
data(){return {n1FromSon:0}},
methods:{
getn1(n1){//通过形参,接收子组件传递过来的数据
this.n1FromSon = n1
}
}
}
父子组件之间数据的双向同步
父组件
<template>
<my-son v-model:num='count'></my-son>
</template>
<script>
import MySon from './Son.vue'
export default{
name:'MyApp',
data(){
return {
count:0,
}
}
}
</script>
子组件
<template>
<p>
子组件{{num}}
</p>
</template>
<script>
export default{
name:'MySon',
props:['num'],
emits:['update:num'],
methods:{
add(){
this.$emit('update:num',this.num + 1)
}
}
}
</script>
Vuex实现数据共享
vuex 是一个专门为vue.js应用程序开发的状态管理模式。
这个状态我们可以理解为在data中的属性,需要共享给其他组件使用的部分。
也就是说,是我们需要共享的data,使用vuex进行统一集中式的管理。
vuex中五种基本对象
- state:存储状态(变量)
- getters:对数据获取之前的再次编译,可以理解为state的计算属性。我们在组件中使用 $store.getters.fun()
- mutations:修改状态,并且是同步的。在组件中使用$store.commit(’’,params)。这个和我们组件中的自定义事件类似。
- actions:异步操作。在组件中使用是$store.dispath(’’)
- modules:store的子模块,为了开发大型项目,方便状态管理而使用的。这里我们就不解释了,用起来和上面的一样。
下载vuex
npm i vuex --save
导入vuex
创建一个index.js
import {createStore} from 'vuex';
export default createStore({
state:{
name:'admin',
print(){
console.log('this is a test')
}
}
})
main.js中注册
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import store from './index.js'
const app = createApp(App)
app.use(store).mount('#app')
调用数据
$store.state.name
使用mutations中的方法
index.js
import {createStore} from 'vuex';
export default createStore({
state:{
name:'admin',
count:0,
print(){
console.log('this is a test')
}
},
mutations:{
addCount(){
return {state.count++}
}
}
})
App.vue
<template>
<div class="hello">
<h3>{{$store.state.count}}</h3>
<div>
<button @click="AddClick()">增加</button>
</div>
</div>
</template>
methods: {
AddClick(){
this.$store.commit('addCount');
},
}
axios
在实际项目开发中,几乎每个组件中都会用到 axios 发起数据请求。此时会遇到如下两个问题:
- 每个组件中都需要导入 axios(代码臃肿)
- 每次发请求都需要填写完整的请求路径(不利于后期的维护)
全局配置axios
在 main.js 入口文件中,通过 app.config.globalProperties 全局挂载 axios,示例代码如下:
axios.defaults.baseURL= 'http://api.com'
app.config.globalProperties.$http = axios
使用方法
this.$http.get('/',{
params:{
name:'',
age:10,
}
})
ref引用
ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,
组件的 $refs 指向一个空对象。
使用ref引用DOM元素
如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:
<h3 ref='myh3'>MyRef组件</h3>
<button @click='getRef'>
获取$refs引用
</button>
meethods:{
getRef(){
//类似于JQuery中的$("#myh3")
console.log(this.$refs.myh3)
//操作DOM元素,把文本颜色改为红色
this.$refs.myh3.style.color = 'red'
}
}
$nextTick
ref获取页面上的DOM元素是异步进行的,所以是当页面元素发生改变重新渲染前获取的话是获取不到DOM元素的,这时就需要使用组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的
DOM 异步地重新渲染完成后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。
<input type='text' v-if='inputVisible' ref='ipt'>
<button v-else @click='showInput'>
</button>
methods:{
showInput(){
this.inputVisible = true
this.$nextTick(() => {
this.$refs.ipt.focus()
})
}
}
动态组件
动态组件指的是动态切换组件的显示与隐藏。vue 提供了一个内置的 <component> 组件,专门用来实现组件
的动态渲染。
- <component> 是组件的占位符
- 通过 is 属性动态指定要渲染的组件名称
- <component is=“要渲染的组件的名称”></component>
data(){
return{
//当前要渲染的组件的名称
conName:'my-dynamic-1'
}
}
<template>
<button @click="conName='my-dynamic-1'">组件1</button>
<button @click="conName='my-dynamic-2'">组件2</button>
<component :is="comName"></component>
</template>
默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 <keep-alive> 组件保持动态组
件的状态。示例代码如下:
<keep-alive>
<component :is="comName"></component>
</keep-alive>
插槽
插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的
部分定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
基础用法
<template>
<p>这是MyCom1 组件的第一个p标签</p>
<!--通过slot标签,为用户预留内容占位符(插槽) -->
<slot></slot>
<p>这是MyCom1 组件的最后一个p标签</p>
</template>
<my-com-1>
<!--为插槽指定具体的内容 -->
<p>~~~用户自定义的内容~~~</p>
</my-com-1>
如果在封装组件时没有预留任何 <slot> 插槽,则用户提供的任何自定义内容都会被丢弃。示例代码如下:
后背内容
封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何
内容,则后备内容会生效。示例代码如下:
<template>
<p>这是MyCom1 组件的第一个p标签</p>
<!--通过slot标签,为用户预留内容占位符(插槽) -->
<slot>这是后备内容</slot>
<p>这是MyCom1 组件的最后一个p标签</p>
</template>
具名插槽
如果在封装组件时需要预留多个插槽节点,则需要为每个 <slot> 插槽指定具体的 name 名称。这种带有具体
名称的插槽叫做“具名插槽”。示例代码如下:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的
形式提供其名称。示例代码如下:
<my-com-2>
<template v-slot:header>
<h1>
TEST
</h1>
</template>
<template v-slot:default>
<p>
this is a test
</p>
</template>
<template v-slot:footer>
<p>
test over.
</p>
</template>
</my-com-2>
v-slot简写
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header
可以被重写为 #header
作用域插槽
在封装组件的过程中,可以为预留的 <slot> 插槽绑定 props 数据,这种带有 props 数据的 <slot> 叫做“作用
域插槽”。示例代码如下:
<div>
<h3>
这是TEST组件
</h3>
<slot :info="information"></slot>
</div>
<!--使用自定义组件 -->
<my-test>
<template v-slot:default="scope">
{{ scope }}
</template>
</my-test>
作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。示例代码如下:
<div>
<h3>
这是TEST组件
</h3>
<slot :info="information"></slot>
</div>
<!--使用自定义组件 -->
<my-test>
<template v-slot:default="{user}">
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.s}}</td>
</template>
</my-test>
自定义指令
vue 中的自定义指令分为两类,分别是:
-
私有自定义指令
-
全局自定义指令
声明私有自定义指令
directives:{
// 自定义一个私有指令
focus:{
//当被绑定的元素插入到DOM中时,自动触发mounted 函数
mounted(el){
el.focus()//让被绑定的元素自动获得焦点
}
}
}
使用自定义指令
<!--声明自定义指令时,指令的名字是focus-->
<!-- 使用自定义指令时,需要加上 v-指令前缀 -->
<input v-focus />
声明全局自定义指令
全局共享的自定义指令需要通过“单页面应用程序的实例对象”进行声明,示例代码如下:
const app = Vue.createApp({})
//注册一个全局自定义指令 v-focus
app.directive('focus',{
mounted(el){
el.focus()
}
})
update函数
mounted 函数只在元素第一次插入 DOM 时被调用,当 DOM 更新时 mounted 函数不会被触发。 updated
函数会在每次 DOM 更新完成后被调用。示例代码如下:
const app = Vue.createApp({})
//注册一个全局自定义指令 v-focus
app.directive('focus',{
mounted(el){//第一次插入DOM时触发这个函数
el.focus()
},
updated(el){//每次DOM更新都会触发updated函数
el.focus()
}
})
注意:在 vue2 的项目中使用自定义指令时,【 mounted -> bind 】【 updated -> update 】
简写
如果 mounted 和updated 函数中的逻辑完全相同,则可以简写成如下格式:
app.directive('focus',(el) =>{
el.focus()
})
指令的参数值
在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值,示例代码如下:
<input type="text" v-model.number="count" v-focus v-color="red">
app.directive('color',(el,binding) =>{
//binding.value 就是通过等号为指令绑定的值
el.style.color = binding.value
})