简介
vue3中使用setup语法糖,父子组件之间相互传递数据及数据同步问题
文章目录
- 简介
- 父传子
- props传递值 使用v-bind绑定
- props需要计算
- toRef
- computed
- emit传递方法 使用v-on绑定
- 子传父
- expose
- v-model
- 总结
父传子
props传递值 使用v-bind绑定
父组件通过props给子组件传递值,props传递的值在子组件中无法修改
// 父组件
<template>
<div style="color: red">
我是父组件
<Child :msg="msg"></Child>
</div>
</template>
<script setup>
import Child from './Child.vue';
import { ref } from 'vue';
const msg = ref('111');
</script>
// 子组件
<template>
<div style="color: blue">
<div>我是子组件, 父组件传递来的值是{{msg}}</div>
</div>
</template>
<script setup>
defineProps({
msg: String,
})
</script>
props需要计算
子组件获取props后,需要显示根据其计算后的值,而props是无法修改的。
toRef
这里有可能会出现子组件只能取到props的初始值,如果props变化子组件不会更新:
// 父组件
<script setup>
import Child from './Child.vue';
import { ref } from 'vue';
const msg = ref('111');
</script>
<template>
<div style="color: red">
我是父组件
{{ msg }}
<Child :msg="msg"></Child>
<button @click="() => msg = '222'">父组件点击改变props值</button>
</div>
</template>
// 子组件
<script setup>
import { ref } from 'vue'
const props = defineProps({
msg: String,
})
const a = ref(props.msg);
</script>
<template>
<div style="color: blue">
<div>我是子组件, 父组件传递来的值是{{a}}</div>
</div>
</template>
点击按钮后,父组件传递的props改变,但是子组件接收到的却不变。
如果父组件中props没有赋初始值,在子组件中接收到的会是undifined。这是因为 ref 是对传入数据的拷贝,但 toRef 是对传入数据的引用。
// 子组件
import { toRef } from 'vue'
const props = defineProps({
msg: String,
})
const a = toRef(props, 'msg');
// const { msg } = toRefs(props); // 使用toRefs也可以
computed
根据props修改可以直接使用计算属性
// 父组件
<script setup>
import Child from './Child.vue';
import { ref } from 'vue';
const msg = ref('A')
</script>
<template>
<div style="color: red">
我是父组件
{{ msg }}
<Child :msg="msg"></Child>
<button @click="() => msg = 'B'">父组件点击改变props值</button>
</div>
</template>
// 子组件
<script setup>
import { computed } from 'vue'
const props = defineProps({
msg: String,
})
const a = computed(() => props.msg.trim().toLowerCase())
</script>
<template>
<div style="color: blue">
<div>我是子组件, 父组件传递来的值是{{ a }}</div>
</div>
</template>
emit传递方法 使用v-on绑定
vue3中子组件想调用父组件传递的方法,需要使用defineEmits
// 父组件
<script setup>
import { ref } from 'vue';
import Child from './Child.vue';
const a = ref('1');
const handleTest = () => {
a.value = 'change';
}
</script>
<template>
<div style="color: red">
我是父组件
<Child :msg="a" @test="handleTest"></Child>
</div>
</template>
// 子组件
<script setup>
defineProps(['msg']);
const emit = defineEmits(["test"])
const handleClick = () => {
emit("test")
}
</script>
<template>
<div style="color: blue">
<div>我是子组件{{ msg }}</div>
<button @click="handleClick">子组件调用父组件方法</button>
</div>
</template>
emit的第一个参数是事件名,第二个参数是传递的参数。
这里如果父组件没有传递这个函数,也不会报错。
子传父
expose
父组件展示子组件的数据与方法,子组件需要通过defineExpose
将自己的值暴露出来,父组件通过子组件上的ref取到其值
// 父组件
<script setup>
import { ref, onMounted } from 'vue';
import Child from './Child.vue';
const a = ref('1');
const x = ref();
onMounted(()=>{
a.value = x.value.message
})
const handle = () => {
x.value.handleMessage();
}
</script>
<template>
<div style="color: red">
我是父组件
<Child ref="x"></Child>
{{ a }}
<button @click="handle">点击子组件触发事件</button>
</div>
</template>
<script setup>
import { ref } from "vue";
const a = ref('我的值是1')
const message = ref('我是子组件暴露的值');
const handleMessage = () => {
a.value = 'Change';
}
defineExpose({
message,
handleMessage
});
</script>
<template>
<div style="color: blue">
我是子组件{{ a }}
</div>
</template>
父组件调用子组件expose的值,一定要在onMounted
之后,否则子组件没有完全加载,取不到值。
这种方法,如果子组件的值修改了,那么父组件也是拿不到的。
v-model
使用v-model
可以实现父子组件之间值的同步。
// 父组件
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1>{{ msg }}</h1>
<Child v-model="msg" />
</template>
// 子组件
<script setup>
const model = defineModel()
</script>
<template>
<span>My input</span> <input v-model="model">
</template>
defineModel是一个编译宏,它相当于:
// 子组件
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<span>My input</span><input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
所以 v-model如果没有另外声明,实际是给子组件设置了一个名为modelValue的props,一个update:modelValue的emit(注意注意,emit调用时第一个参数一定要跟define时相同,否则找不到,update:后面也不要加空格)。
v-model
其实相当于v-bind
和v-on
的组合:
<template>
<h1>{{ msg }}</h1>
<Child :modelValue="msg" @update:modelValue="val => msg= val"></Child>
</template>
v-model
会将modelValue
这个props和update:modelValue
这个event绑定在一起。当子组件的值发生变化时,会触发update:modelValue
事件传递给父组件,父组件接收到事件后会更新自己的值并重新渲染子组件;当父组件的值发生变化时,会通过modelValue
传递给子组件,子组件接收到 prop 后会更新自己的值并重新渲染。这样就实现了父子组件之间的数据同步。
v-model可以写成v-model:自定义='自定义'
,那么更新的值就是自定义
,更新的函数就是 update:自定义
总结
- 父传子:
defineProps
、defineEmits
- 子传父:
defineExpose
- 双向绑定:
v-bind
vue中父子传值的方法还是非常多的,但是其中不乏各种坑,新手还是应该老老实实用官方推荐,否则真的很难不踩坑,太灵活了有些时候也是一种问题呢(无语笑)。