watch 侦听器
什么是 watch 侦听器
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。例如,监视用户名的变化并发起请求,判断用户名是否可用。
watch 侦听器的基本语法
开发者需要在 watch 节点下,定义自己的侦听器。实例代码如下:
<template>
用户名:<input type="text" class="form-control"
v-model.trim="username" />
</template>
<script>
export default{
name:'MyWatch',
data(){
return{
username:'',
}
},
watch:{
//侦听username的变化
//参数1是新值,参数2是老值
username(newVal,oldVal){
console.log(newVal,oldVal);
}
}
}
</script>
<style>
</style>
使用 watch 检测用户名是否可用
监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用。
immediate 选项
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。
deep 选项
当 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项
监听对象单个属性的变化
如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:
计算属性 vs 侦听器
计算属性和侦听器侧重的应用场景不同:
计算属性侧重于监听多个值的变化,最终计算并返回一个新值
侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值
<template>
用户名:<input type="text" class="form-control"
v-model.trim="username" />{{msg}}
</template>
<script>
export default{
name:'MyWatch',
data(){
return {
username:'',
msg:'',
}
},
watch:{
// 侦听username的变化
// 参数1是新值,参数2是老值
username(newVal,oldVal){
// console.log(newVal,oldVal);
var api = 'https://www.escook.cn/api/finduser/'+newVal
// axios向后台项目发出get请求,返回数据为response
axios.get(api).then( function(response){
// response.data是返回的数据
//console.log(response.data);
if(response.data.status==1){
this.msg = response.data.messsage
}
} )
}
}
}
</script>
<style>
</style>
watch的生命周期
组件运行的过程
如何监听组件的不同时刻
vue 框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。例如:
① 当组件在内存中被创建完毕之后,会自动调用 created 函数
② 当组件被成功的渲染到页面上之后,会自动调用 mounted 函数
③ 当组件被销毁完毕之后,会自动调用 unmounted 函数
如何监听组件的更新
当组件的 data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证 View 视图展示的数据和Model 数据源保持一致。
当组件被重新渲染完毕之后,会自动调用updated 生命周期函数。
组件中主要的生命周期函数
注意:在实际开发中,created 是最常用的生命周期函数!
App.vue
<template>
<h1>App根组件</h1>
<life-cycle></life-cycle>
</template>
<script>
import LifeCycle from './LifeCycle.vue'
// render 渲染
export default{
name:'App',
components:{
LifeCycle,
},
data(){
return{
}
},
}
</script>
<style>
</style>
LifeCycle.vue
<template>
<h2>LifeCycle组件</h2>
<input type="text" v-model="username" /><br />
{{username}}
</template>
<script>
export default{
name:'LifeCycle',
data(){
return {
username:'',
}
},
// 组件在内存中创建完毕后执行
created(){
console.log("created:组件在内存中创建完毕");
},
// 组件渲染到页面完毕
mounted(){
console.log("mounted:组件渲染到页面完毕")
},
updated(){
console.log("updated:data数据完成更新")
},
// 组件被销毁完毕
unmounted(){
console.log("unmounted:组件被销毁完毕")
}
}
</script>
<style>
</style>
效果如下:
2、
<template>
<h2>LifeCycle组件</h2>
<input type="text" v-model="username" /><br />
{{username}}
</template>
<script>
export default{
name:'LifeCycle',
data(){
return {
username:'kk',
}
},
// 在内存中开始创建组件之前
beforeCreate(){
console.log("beforeCreate:在内存中开始创建组件之前")
console.log(this.username) // undefined
},
// 组件在内存中创建完毕后执行
created(){
console.log("created:组件在内存中创建完毕")
console.log(this.username) // kk
},
// 在把组件初次渲染到页面之前
beforeMount(){
console.log("beforeMount:在把组件初次渲染到页面之前")
console.log(document.body.innerHTML)
},
// 组件渲染到页面完毕
mounted(){
console.log("mounted:组件渲染到页面完毕")
console.log(document.body.innerHTML)
},
updated(){
console.log("updated:data数据完成更新")
},
// 组件被销毁完毕
unmounted(){
console.log("unmounted:组件被销毁完毕")
}
}
</script>
<style>
</style>
<template>
<h1>App根组件</h1>
<life-cycle v-if="flag"></life-cycle>
<button @click="flag=!flag">切换组件的创建和销毁</button>
</template>
<script>
import LifeCycle from './LifeCycle.vue'
// render 渲染
export default{
name:'App',
components:{
LifeCycle,
},
data(){
return{
flag:true,
}
},
}
</script>
<style>
</style>
效果如下:
组件之间的数据共享
1、父组件向子组件共享数据
父组件通过 v-bind 属性绑定向子组件共享数据。同时,子组件需要使用 props 接收数据。示例代码如下:
father-to-son
//Father
<template>
{{message}}--{{userinfo.name}}--{{userinfo.age}}
<hr />
<son :msg="message" :user="userinfo"></son>
</template>
<script>
import Son from './Son.vue'
export default{
name:'Father',
components:{
Son,
},
data(){
return {
message:'hello vue',
userinfo:{
name:'tom',
age:12
}
}
}
}
</script>
<style>
</style>
//Son
<template>
{{msg}}
<br />
{{user.name}}
</template>
<script>
export default{
name:'Son',
props:['msg','user'],
}
</script>
<style>
</style>
其运行效果为:
2、子组件向父组件共享数据
子组件通过自定义事件的方式向父组件共享数据。示例代码如下
son-to-father
//Father
<template>
{{n1FromSon}}
<hr />
<son @n1change="getn1"></son>
</template>
<script>
import Son from './Son.vue'
export default{
name:'Father',
components:{
Son,
},
data(){
return{
n1FromSon:0,
}
},
methods:{
getn1(n1){
this.n1FromSon = n1
}
}
}
</script>
<style>
</style>
//Son
<template>
n1:{{n1}}
<button @click="addN1">n1++</button>
</template>
<script>
export default{
name:'Son',
emits:['n1change'],
data(){
return{
n1:0
}
},
methods:{
addN1(){
this.n1++
this.$emit('n1change',this.n1)
}
}
}
</script>
<style>
</style>
其运行效果为:
3、父子组件之间数据的双向同步
//Father
<template>
<button @click="count+=1"> +1 </button>
<br />
count: {{count}}
<hr />
<son v-model:num="count"></son>
</template>
<script>
import Son from './Son.vue'
export default{
name:'App',
data(){
return{
count:0
}
},
components:{
Son,
}
}
</script>
<style>
</style>
//Son
<template>
num:{{num}}
<button @click="add"> +1 </button>
</template>
<script>
export default{
name:'Son',
props:['num'], // props里面的数据是外部传入的,不能直接修改!!!
// 声明自定义事件update:num,
// 注意这个事件的名称格式为update:xxxx
// xxxx表示修改了名字为xxxx的属性
emits:['update:num'],
methods:{
add(){
// 触发update:num事件,修改num属性值为this.num+1
this.$emit('update:num',this.num+1)
}
}
}
</script>
<style>
</style>
效果如下:
4、兄弟组件之间的数据共享
兄弟组件之间实现数据共享的方案是 EventBus。可以借助于第三方的包 mitt 来创建 eventBus 对象,从而实现兄弟组件之间的数据共享。示意图如下:
4.1 安装 mitt 依赖包
在项目中运行如下的命令,安装 mitt 依赖包:
4.2 创建公共的 EventBus 模块
在项目中创建公共的 eventBus
模块如下:
4.3 在数据接收方自定义事件
在数据接收方,调用 bus.on
(‘事件名称’, 事件处理函数) 方法注册一个自定义事件。示例代码如下:
4.4 在数据接发送方触发事件
在数据发送方,调用 bus.emit
(‘事件名称’, 要发送的数据) 方法触发自定义事件。示例代码如下:
兄弟组件案例:
//App.vue
<template>
<div>
<h1>App 根组件</h1>
<hr />
<h1>
注意:需要先写接收方组件,再写发送方组件
两个组件中的事件名字必须相同!!!!!!
</h1>
<div class="brother-box">
<!-- 3. 使用组件 -->
<my-left></my-left>
<my-right></my-right>
</div>
</div>
</template>
<script>
// 1. 导入需要的组件
import MyLeft from './MyLeft.vue'
import MyRight from './MyRight.vue'
export default{
name:"App",
// 2. 注册组件
components: {
MyLeft,
MyRight,
},
}
</script>
<style lang="css" scoped="scoped">
.brother-box {
display: flex;
}
.brother-box > div {
border: 1px solid #ccc;
flex: 1;
margin: 10px;
padding: 10px;
height: 300px;
}
</style>
//eventBus.js
import mitt from 'mitt'
const bus = mitt()
export default bus
//MyLeft.vue
<template>
<div>
<h3>数据发送方 --- count 的值为:{{ count }}</h3>
<button type="button" class="btn btn-primary" @click="add">+1</button>
</div>
</template>
<script>
import bus from './eventBus.js'
export default {
name: 'MyLeft',
data() {
return {
count: 0,
}
},
methods: {
add() {
this.count++
bus.emit('countChange', this.count)
},
},
}
</script>
<style>
</style>
//MyRight.vue
<template>
<div>
<h3>数据接收方 --- num 的值为:{{ num }}</h3>
</div>
</template>
<script>
import bus from './eventBus.js' // 导入js文件中的bus
export default {
name: 'MyRight',
data() {
return {
num: 0,
}
},
created() { // 生命周期函数created
// 接收方定义自定义事件countChange
// bus.on('事件名称',事件处理函数)
// count是事件传过来的参数
let a = this;
bus.on('countChange', function(count) {
a.num = count
})
},
}
</script>
<style lang="less" scoped></style>
//readme.txt
如何使用mitt?
步骤1:在当前工程code4下执行
E:\XJA\vue\code4> npm install --save mitt
步骤2:编写代码,导入mitt,创建mitt对象
import mitt from 'mitt'
const bus = mitt()
可以把这些代码创建一个js文件,如eventBus.js,代码如下
import mitt from 'mitt'
const bus = mitt()
export default bus
步骤3:
vue文件中使用mitt
import bus from './eventBus.js'
。。。。
在数据接收方自定义事件
bus.on('事件名称',事件处理函数)
在数据发送方触发事件
bus.emit('事件名称', 要发送的数据)
运行效果如下:
5. 后代关系组件之间的数据共享
后代关系组件之间共享数据,指的是父节点的组件向其子孙组件共享数据。此时组件之间的嵌套关系比较复杂,可以使用 provide
和 inject
实现后代关系组件之间的数据共享。
5.1 父节点通过 provide 共享数据
父节点的组件可以通过provide 方法,对其子孙组件共享数据:
5.2 子孙节点通过 inject 接收数据
子孙节点可以使用 inject
数组,接收父级节点向下共享的数据。示例代码如下:
5.3 父节点对外共享响应式的数据
父节点使用 provide 向下共享数据时,可以结合computed
函数向下共享响应式的数据。示例代码如下:
5.4 子孙节点使用响应式的数据
如果父级节点共享的是响应式的数据,则子孙节点必须以 .value
的形式进行使用。示例代码如下:
6. vuex
vuex 是终极的组件之间的数据共享方案。在企业级的 vue 项目开发中,vuex 可以让组件之间的数据共享变得高效、清晰、且易于维护。
后代关系组件案例—06provide-inject
//App.vue
<template>
<div>
<h1>App根组件--{{color}}</h1>
<hr />
<TwoLevel></TwoLevel>
</div>
</template>
<script>
import TwoLevel from './TwoLevel.vue'
export default{
name:"App",
components:{
TwoLevel
},
data(){
return {
color:"blue"
}
},
// 父节点的组件可以通过 provide 方法,对其子孙组件共享数据
provide(){
return{
color:this.color, // color共享
age:12, // age共享
}
},
}
</script>
<style lang="css" scoped="scoped">
</style>
//ThreeLevel.vue
<template>
<div>
<h3>ThreeLevel组件---{{color}}---{{age}}</h3>
</div>
</template>
<script>
export default{
name:"ThreeLevel",
// 子孙节点通过 inject 接收数据
inject:['color','age']
}
</script>
<style lang="css" scoped="scoped">
</style>
//TwoLevel.vue
<template>
<div>
<h2>TwoLevel组件</h2>
<hr />
<ThreeLevel></ThreeLevel>
</div>
</template>
<script>
import ThreeLevel from './ThreeLevel.vue'
export default{
name:"TwoLevel",
components:{
ThreeLevel
},
}
</script>
<style lang="css" scoped="scoped">
</style>
其运行效果如下:
vue 3.x 中全局配置 axios
1. 为什么要全局配置 axios
在实际项目开发中,几乎每个组件中都会用到 axios 发起数据请求。此时会遇到如下两个问题:
① 每个组件中都需要导入axios(代码臃肿)
② 每次发请求都需要填写完整的请求路径(不利于后期的维护)
2. 如何全局配置 axios
在 main.js 入口文件中,通过 app.config.globalProperties
全局挂载 axios,示例代码如下:
axios案例
//readme.txt
全局配置axios
步骤1:index.html
<!-- 引入axios -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
注意:不需要执行npm i axios -S
步骤2:main.js
const spa_app = createApp(App)
// 指定axios根路径
axios.defaults.baseURL = "https://www.escook.cn"
spa_app.mount('#app')
步骤3:代码
post请求
methods:{
postInfo(){
let api = "/api/post"
// post请求以json对象形式传参
axios.post(api,{name:'zs',age:12}).then(function(response) {
// response是所有响应数据,真正返回的数据是response.data
console.log(response.data)
})
}
}
get请求
methods:{
getInfo(){
// get请求以json对象形式传参,但是必须在外面加上params
let api = '/api/get';
// get请求以json对象形式传参,但是必须在外面加上params
axios.get(api,{params:{name:'zs',age:12}}).then(function(response) {
// response是所有响应数据,真正返回的数据是response.data
console.log(response.data)
})
}
}
注意:// 导入 axios
import axios from 'axios'这句话可以不用写!!!!
//App.vue
<template>
<button @click="getInfo">get请求</button>
<hr />
<button @click="postInfo">post请求</button>
</template>
<script>
export default{
name:'App',
methods:{
// get请求
getInfo(){
// 请求路径
let api = "/api/get"
// get提交,参数1是路径,参数2是json格式的上传参数
axios.get(api,{params:{name:'zz',age:14}}).then(function(response){
console.log(response.data)
})
},
// post请求
postInfo(){
// 请求路径
let api = "/api/post"
// post提交,参数1是路径,参数2是json格式的上传参数
axios.post(api,{name:'zs',age:12}).then(function(response){
console.log(response.data)
})
}
}
}
</script>
<style>
</style>
//main.js
import { createApp } from 'vue'
//import App from './App.vue'
//import App from './components/01life-cycle/App.vue'
//import App from './components/05brother-eventbus/App.vue'
//import App from './components/06provide-inject/App.vue'
import App from './components/07axios/App.vue'
import './index.css'
const spa_app = createApp(App)
// 指定axios根路径
axios.defaults.baseURL = "https://www.escook.cn"
spa_app.mount('#app')
其运行效果为