组件传值 props
Props 是一种特别的 attributes,你可以在组件上声明注册。要传递给子组件一个属性,我们必须在组件的 props 列表上声明它。这里要用到
defineProps宏,defineProps是一个仅<script setup>中可用的编译宏命令,并不需要显式地导入。声明的 props 会自动暴露给模板。
<!-- chlid.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
子组件调用父组件方法 $emit
<!-- 父组件文件 -->
<BlogPost
...
@enlarge-text="postFontSize += 0.1" //监听
/>
<!-- 子组件文件 -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
//触发父组件中的 "enlarge-text" 事件
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
单向数据流
vue中的props是单向数据流原则,props 因父组件的更新而流传到子组件,但不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
但开发过程中有些场景需要修改父组件船体过来的props,比如
- prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter'])
// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)
- 需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性
const props = defineProps(['size'])
// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
v-modal
vue3中提供了一个宏 defineModel()
<!-- Child.vue -->
<script setup>
const model:msg = defineModel('msg')
const model:msg11 = defineModel('msg11')
</script>
<template>
<input v-model="msg" />
</template>
父组件可以用 v-model 绑定一个值:
<!-- Parent.vue -->
<script setup>
const msg = ref("hello")
const msg11 = ref("bye")
</script>
<Child v-model:msg="msg" v-model:msg11="msg11 />
这样就可以达到子组件中的属性model发生变化,父组件中的属性msg会同步更新的效果
透传Attributes
透传Attributes这个名词,乍一听挺懵的,其实它就是在父组件中传入的属性,没有声明为prop,而自动透传到了子组件的根元素上,例如下面的class属性
<!-- <MyButton> 的模板 -->
<button>click me</button>
//parent.vue
<MyButton class="large" />
最后渲染出的 DOM 结果是:
<button class="large">click me</button>
一般能够透传的Attributes有:class,style,v-on监听器
同样的规则也适用于 v-on 事件监听器:
<MyButton @click="onClick" />
click 监听器会被添加到 <MyButton> 的根元素,即那个原生的 <button> 元素之上。当原生的 <button> 被点击,会触发父组件的 onClick 方法。同样的,如果原生 button 元素自身也通过 v-on 绑定了一个事件监听器,则这个监听器和从父组件继承的监听器都会被触发。
深层组件继承
有些情况下一个组件会在根节点上渲染另一个组件。例如,我们重构一下 <MyButton>,让它在根节点上渲染 <BaseButton>:
<!-- <MyButton/> 的模板,只是渲染另一个组件 -->
<BaseButton />
禁用 Attributes 继承
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false。
从 3.3 开始你也可以直接在 <script setup> 中使用 defineOptions:
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
这些透传进来的 attribute 可以在模板的表达式中直接用
$attrs访问到。
<span>Fallthrough attribute: {{ $attrs }}</span>
有几点需要注意:
- 和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像
foo-bar这样的一个 attribute 需要通过$attrs['foo-bar']来访问。 - 像
@click这样的一个v-on事件监听器将在此对象下被暴露为一个函数$attrs.onClick。
我们想要所有像 class 和 v-on 监听器这样的透传 attribute 都应用在内部的 <button> 上而不是外层的 <div> 上。我们可以通过设定 inheritAttrs: false 和使用 v-bind="$attrs" 来实现:
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">click me</button>
</div>
小提示:没有参数的 v-bind 会将一个对象的所有属性都作为 attribute 应用到目标元素上。
在 JavaScript 中访问透传 Attributes
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
需要注意的是,虽然这里的 attrs 对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。你不能通过侦听器去监听它的变化。如果你需要响应性,可以使用 prop。或者你也可以使用 onUpdated() 使得在每次更新时结合最新的 attrs 执行副作用。
provide reject
provide 和 inject 可以实现 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
provide()为组件后代提供数据
//父组件
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
inject()注入上层组件提供的数据
//子组件
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
有的时候,我们可能需要在注入方组件中更改数据。在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数:
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
最后,如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>