文章目录
- 一、 r e f s 和 refs和 refs和parent的概念及使用场景
- 1. $refs概念及使用场景
- 2. $parent概念及使用场景
- 二、代码解释
- Father.vue
- Child1.vue
- Child2.vue
- 三、新的例子
- CounterParent.vue
- CounterIncrement.vue
- CounterDecrement.vue
一、 r e f s 和 refs和 refs和parent的概念及使用场景
1. $refs概念及使用场景
-
概念:在Vue 3.0里,
$refs
是一个比较特殊的属性,它的主要作用是让我们能够访问到组件实例或者DOM元素。具体来讲,当我们在模板中给一个组件或者DOM元素设置了ref
属性之后,就可以通过组件实例的$refs
这个对象来拿到对该组件或者DOM元素的引用啦。 -
使用场景:
-
访问子组件实例:在父组件当中,常常会用到
$refs
来获取子组件的实例哦。为什么要这么做呢?因为这样一来,父组件就可以直接去访问子组件内部的数据,还能调用子组件的方法呢。比如说,在一些场景下,父组件需要根据用户的操作来触发子组件里的某个特定方法,或者要获取子组件中的某个数据值以便做进一步的处理,这时候$refs
就派上大用场啦。 -
操作DOM元素:除了获取子组件实例,
$refs
还有个用处就是能拿到对普通DOM元素的引用哦。有了这个引用,我们就可以直接对DOM元素进行一些操作啦,比如获取输入框里的值呀,或者给元素设置样式之类的。不过呢,在Vue的开发理念里,通常还是建议尽量通过数据绑定和响应式机制来操作视图哦,直接去操作DOM元素的情况相对来说是比较少的,但在某些特定的需求下,还是会用到$refs
来操作DOM元素的呢。
-
访问子组件实例:在父组件当中,常常会用到
2. $parent概念及使用场景
-
概念:
$parent
是一个组件实例的属性哦,它是指向当前组件的父组件实例的。通过$parent
这个属性,子组件就能够去访问父组件的属性,还能调用父组件的方法呢,这样就实现了子组件和父组件之间从子到父方向的通信啦。 -
使用场景:
-
子组件修改父组件数据:有时候子组件需要根据自己这边的操作来更新父组件里的某些数据呀,这时候就可以利用
$parent
来访问父组件实例,然后进而去修改父组件的数据哦。比如说,子组件里的某个操作可能会影响到父组件展示的总体数据状态,那通过$parent
就能很方便地实现这种跨组件的数据更新啦。 -
子组件调用父组件方法:同样的道理,子组件要是需要触发父组件中定义的某个方法来完成特定的业务逻辑,比如子组件完成了某项任务之后要通知父组件接着做后续处理呀,这时候就可以通过
$parent
来调用父组件的方法啦。
-
子组件修改父组件数据:有时候子组件需要根据自己这边的操作来更新父组件里的某些数据呀,这时候就可以利用
二、代码解释
Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>房产:{{ house }}</h4>
<button @click="changeToy">修改Child1的玩具</button>
<button @click="changeComputer">修改Child2的电脑</button>
<button @click="getAllChild($refs)">让所有孩子的书变多</button>
<Child1 ref="c1" />
<Child2 ref="c2" />
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref, reactive } from "vue";
let c1 = ref()
let c2 = ref()
// 注意点:当访问obj.c的时候,底层会自动读取value属性,因为c是在obj这个响应式对象中的
/* let obj = reactive({
a:1,
b:2,
c:ref(3)
})
let x = ref(4)
console.log(obj.a)
console.log(obj.b)
console.log(obj.c)
console.log(x) */
// 数据
let house = ref(4)
// 方法
function changeToy() {
c1.value.toy = '小猪佩奇'
}
function changeComputer() {
c2.value.computer = '华为'
}
function getAllChild(refs: { [key: string]: any }) {
console.log(refs)
for (let key in refs) {
refs[key].book += 3
}
}
// 向外部提供数据
defineExpose({ house })
</script>
<style scoped>
.father {
background-color: rgb(165, 164, 164);
padding: 20px;
border-radius: 10px;
}
.father button {
margin-bottom: 10px;
margin-left: 10px;
}
</style>
- 在
Father.vue
这个组件里面呢:-
使用$refs获取子组件实例并操作:
- 在模板部分呀,给
Child1
组件设置了ref="c1"这个属性,给Child2组件设置了ref="c2"属性。这么做了之后呢,在父组件的脚本部分就能通过 r e f s 来拿到这两个子组件的实例啦。比如说,在 c h a n g e T o y 这个方法里面,通过 ‘ c 1. v a l u e . t o y = ′ 小猪佩 奇 ′ ‘ 就把 ‘ C h i l d 1 ‘ 组件里的 ‘ t o y ‘ 数据给修改了;在 ‘ c h a n g e C o m p u t e r ‘ 方法里,通过 ‘ c 2. v a l u e . c o m p u t e r = ′ 华 为 ′ ‘ 就把 ‘ C h i l d 2 ‘ 组件里的 ‘ c o m p u t e r ‘ 数据给修改了。这里的 ‘ c 1. v a l u e ‘ 和 ‘ c 2. v a l u e ‘ 其实就是通过 ‘ refs来拿到这两个子组件的实例啦。比如说,在changeToy这个方法里面,通过`c1.value.toy = '小猪佩奇'`就把`Child1`组件里的`toy`数据给修改了;在`changeComputer`方法里,通过`c2.value.computer = '华为'`就把`Child2`组件里的`computer`数据给修改了。这里的`c1.value`和`c2.value`其实就是通过` refs来拿到这两个子组件的实例啦。比如说,在changeToy这个方法里面,通过‘c1.value.toy=′小猪佩奇′‘就把‘Child1‘组件里的‘toy‘数据给修改了;在‘changeComputer‘方法里,通过‘c2.value.computer=′华为′‘就把‘Child2‘组件里的‘computer‘数据给修改了。这里的‘c1.value‘和‘c2.value‘其实就是通过‘refs`获取到的子组件实例呀,拿到实例之后呢,就可以像操作普通对象一样去操作它们内部的数据啦。 - 在getAllChild这个方法里呢,参数
refs: { [key: string]: any }
接收的就是通过$refs获取到的所有带有ref属性的组件或者元素的引用对象哦。然后通过遍历这个对象,对每个子组件实例的book数据都进行了修改(refs[key].book += 3
),这样就实现了对所有子组件中book
数据的统一操作啦。
- 在模板部分呀,给
-
向外部提供数据:通过
defineExpose({ house })
这句代码呀,父组件就把house这个数据给暴露出去啦,这样在其他需要的时候,比如可能存在的更外层的组件呀,就可以获取到这个数据啦。
-
使用$refs获取子组件实例并操作:
Child1.vue
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具:{{ toy }}</h4>
<h4>书籍:{{ book }} 本</h4>
<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
</div>
</template>
<script setup lang="ts" name="Child1">
import { ref } from "vue";
// 数据
let toy = ref('奥特曼')
let book = ref(3)
// 方法
function minusHouse(parent: any) {
parent.house -= 1
}
// 把数据交给外部
defineExpose({ toy, book })
</script>
<style scoped>
.child1 {
margin-top: 20px;
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>
在Child1.vue
组件这里呢:
使用
p
a
r
e
n
t
访问父组件并修改数据:在
m
i
n
u
s
H
o
u
s
e
这个方法里面,通过
p
a
r
e
n
t
.
h
o
u
s
e
−
=
1
就把父组件
F
a
t
h
e
r
.
v
u
e
里的
h
o
u
s
e
数据给修改了。这里的
p
a
r
e
n
t
其实就是通过
parent访问父组件并修改数据 : 在minusHouse这个方法里面,通过parent.house -= 1就把父组件Father.vue里的house数据给修改了。这里的parent其实就是通过
parent访问父组件并修改数据:在minusHouse这个方法里面,通过parent.house−=1就把父组件Father.vue里的house数据给修改了。这里的parent其实就是通过parent获取到的父组件实例哦,通过这样的方式呢,就实现了子组件对父组件数据的反向操作啦。
向外部提供数据:
同样也是通过defineExpose({ toy, book })
这句代码呀,把toy和book这两个数据给暴露出去啦,这样其他组件要是想获取或者操作这两个数据就方便多啦。
Child2.vue
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑:{{ computer }}</h4>
<h4>书籍:{{ book }} 本</h4>
</div>
</template>
<script setup lang="ts" name="Child2">
import { ref } from "vue";
// 数据
let computer = ref('联想')
let book = ref(6)
// 把数据交给外部
defineExpose({ computer, book })
</script>
<style scoped>
.child2 {
margin-top: 20px;
background-color: orange;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>
- 在
Child2.vue
组件里呢,通过defineExpose({ computer, book })把computer和book这两个数据给暴露出去啦,虽然这里不像Child1.vue那样通过$parent和父组件进行交互呀,但也是遵循了把组件内部数据合理暴露出去以便外部使用的这么一个原则哦。
三、新的例子
假设我们现在有一个简单的计数器应用哦,它包含了一个父组件CounterParent.vue
以及两个子组件CounterIncrement.vue
(这个子组件是用来增加计数的)和CounterDecrement.vue
(这个子组件是用来减少计数的)。
CounterParent.vue
<template>
<div class="counter-parent">
<h2>计数器:{{ count }}</h2>
<CounterIncrement ref="incrementRef" />
<CounterDecrement ref="decrementRef" />
</div>
</template>
<script setup lang="ts">
import CounterIncrement from './CounterIncrement.vue';
import CounterDecrement from './CounterDecrement.vue';
import { ref } from 'vue';
let count = ref(0);
let incrementRef = ref();
let decrementRef = ref();
// 当点击增加按钮时,调用子组件的增加方法并更新计数
const incrementCount = () => {
incrementRef.value.increment();
count.value++;
};
// 当点击减少按钮时,调用子组件的减少方法并更新计数
const decrementCount = () => {
decrementRef.value.decrement();
count.value--;
};
</script>
<style scoped>
.counter-parent {
background-color: lightgray;
padding: 20px;
border-radius: 10px;
}
</style>
在CounterParent.vue
这个组件里面呢:
- 首先定义了count作为计数器的初始值哦,并且通过ref创建了incrementRef和decrementRef这两个东西,它们的作用就是用来获取那两个子组件的实例呀。
- 然后呢,
incrementCount
这个方法呀,它是通过$refs拿到CounterIncrement.vue子组件的实例(也就是incrementRef.value),然后调用它的increment方法来增加计数的哦,同时呢,还会把父组件中的count值也给更新一下。 - 同样的,
decrementCount
这个方法呢,也是通过$refs拿到CounterDecrement.vue子组件的实例(也就是decrementRef.value),调用它的decrement方法来减少计数的哦,并且也会更新count值呢。
CounterIncrement.vue
<template>
<button @click="increment">增加计数</button>
</template>
<script setup lang="ts">
import { defineExpose } from 'vue';
// 定义增加计数的方法
const increment = () => {
// 这里可以添加一些额外的逻辑,比如发送通知等
console.log('计数增加');
};
// 向外部暴露增加计数的方法
defineExpose({ increment });
</script>
<style scoped>
button {
margin-right: 10px;
}
</style>
在CounterIncrement.vue
这个组件里面呢:
- 首先定义了increment这个方法呀,它的作用就是用来增加计数的哦。
- 然后通过
defineExpose({ increment })
这句代码呀,就把increment这个方法给暴露出去啦,这样父组件就能够调用这个方法啦。
CounterDecrement.vue
<template>
<button @click="decrement">减少计数</button>
</template>
<script setup lang="ts">
import { defineExpose } from 'vue';
// 定义减少计数的方法
const decrement = () => {
// 以下可以添加一些额外的逻辑,比如发送通知等
console.log('计数减少');
};
// 向外部暴露减少计数的方法
defineExpose({ decrement });
</script>
<style scoped>
button {
margin-right: 10px;
}
</style>
在CounterDecrement.vue
这个组件里面呢:
- 首先定义了decrement这个方法呀,它的作用就是用来减少计数的哦。
- 然后通过defineExpose({ decrement })这句代码呀,就把decrement这个方法给暴露出去啦,这样父组件就能够调用这个方法啦。
在这个新例子里面呢,通过$refs实现了父组件对子组件方法的调用呀,这样就完成了计数器的增加和减少操作啦;同时呢,子组件通过defineExpose把内部方法暴露给父组件,这也是遵循了Vue 3.0组件间交互的规范哦。