文章目录
如果你想边看边实验,并且不行单独创建vue项目,我想vue官网自带的演练场很适合你,请点这里!
1、父组件给子组件传值
1.1 子组件声明属性
在使用 <script setup>
的单文件组件中,props(属性s) 可以使用 defineProps()
宏来声明:
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
除了使用字符串数组来声明 prop(属性) 外,还可以使用对象的形式:
// 使用 <script setup>
const props = defineProps({
title: String,
likes: Number
})
如果一个 prop(属性) 的名字很长,应使用 camelCase(驼峰命名法)形式,因为它们是合法的 JavaScript 标识符,可以直接在模板的表达式中使用,也可以避免在作为属性 key 名时必须加上引号。
const props = defineProps({
greetingMessage: String
})
<span>{{ greetingMessage }}</span>
虽然理论上你也可以在向子组件传递 props 时使用 camelCase (驼峰命名法)形式,但实际上我们通常会将其写为 kebab-case (说人话:将大写字母小写用“-”连接两个单词)形式:
父组件使用子组件示例:
<MyComponent greeting-message="hello" />
一些补充细节:
- 所有 prop 默认都是可选的,除非声明了
required: true
。 - 除
Boolean
外的未传递的可选 prop 将会有一个默认值undefined
。 Boolean
类型的未传递 prop 将被转换为false
。这可以通过为它设置default
来更改——例如:设置为default: undefined
将与非布尔类型的 prop 的行为保持一致。- 如果声明了
default
值,那么在 prop 的值被解析为undefined
时,无论 prop 是未被传递还是显式指明的undefined
,都会改为default
值。
当 prop 的校验失败后,Vue 会抛出一个控制台警告 (在开发模式下)。
如果使用了基于类型的 prop 声明 ,Vue 会尽最大努力在运行时按照 prop 的类型标注进行编译。举例来说,defineProps<{ msg: string }>
会被编译为 { msg: { type: String, required: true }}
。
如果你对Prop属性声明时有类型要求可以参考基于类型的Prop声明ts
1.2 父组件向子组件传递多种形式的值
至此,你已经见过了很多像这样的静态值形式的 props:
<BlogPost title="My journey with Vue" />
相应地,还有使用 v-bind
或缩写 :
来进行动态绑定的 props:
<!-- 根据一个变量的值动态传入 -->
<BlogPost :title="post.title" />
<!-- 根据一个更复杂表达式的值动态传入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />
传递不同的值类型
在上述的两个例子中,我们只传入了字符串值,但实际上任何类型的值都可以作为 props 的值被传递。
1.2.1 Number
<!-- 虽然 `42` 是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :likes="42" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :likes="post.likes" />
1.2.2 Boolean
<!-- 仅写上 prop 但不传值,会隐式转换为 `true` -->
<BlogPost is-published />
<!-- 虽然 `false` 是静态的值,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :is-published="false" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :is-published="post.isPublished" />
1.2.3 Array
<!-- 虽然这个数组是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :comment-ids="[234, 266, 273]" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :comment-ids="post.commentIds" />
1.2.4 Object
<!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
/>
<!-- 根据一个变量的值动态传入 -->
<BlogPost :author="post.author" />
1.2.4 使用一个对象绑定多个 prop
如果你想要将一个对象的所有属性都当作 props 传入,你可以使用没有参数的 v-bind
,即只使用 v-bind
而非 :prop-name
。例如,这里有一个 post
对象:
const post = {
id: 1,
title: 'My Journey with Vue'
}
以及下面的模板:
<BlogPost v-bind="post" />
而这实际上等价于:
<BlogPost :id="post.id" :title="post.title" />
1.3 在子组件中如何使用父组件的值
示例:
父组件 App.vue
:
<template>
<!-- 使用子组件,并且向子组件声明的属性中传入值 -->
<Comp name="张三"></Comp>
</template>
<script setup>
//引入同级目录的子组件
import Comp from './Comp.vue';
</script>
子组件 Comp.vue
:
<template>
<!-- 我们可以在template中直接使用 -->
{{name}}
</template>
<script setup>
// 声明属性 name
const props = defineProps({name:String})
//当然我们在用props调用属性值
console.log(props.name)
</script>
单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告:
const props = defineProps(['foo'])
// ❌ 警告!prop 是只读的!
props.foo = 'bar'
导致你想要更改一个 prop 的需求通常来源于以下场景:
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter'])
// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter
2、子组件向父组件传值
如果你点开了上边的网址请不要怀疑,官网为组件事件。
2.1 触发与监听事件
在组件的模板表达式中,可以直接使用 defineEmits
定义自定义事件:
<template>
<button @click="click">按钮</button>
</template>
<script setup>
// 声明子组件自定义事件,可以声明多个事件
const emit = defineEmits(['someEvent'])
const click =()=>{
// 触发事件 向父组件发送触发事件名称和携带参数
//当然我们在向父组件发送触发事件时也可以发送vue自带的事件
emit('someEvent','hello')
}
</script>
父组件可以通过 v-on
(缩写为 @
) 来监听事件:
<template>
<!-- 父组件监听子组件事件,只要子组件发送相应事件名称就是代表触发了相应事件 -->
<Comp @some-event="callback"></Comp>
</template>
<script setup>
import Comp from './Comp.vue'
// 触发子组件自定义事件之后调用函数拿出子组件传的值
const callback = (res)=>{
console.log(res)
}
</script>
像组件与 prop 一样,事件的名字也提供了自动的格式转换。注意这里我们触发了一个以 camelCase(驼峰命名) 形式命名的事件,但在父组件中可以使用 kebab-case 形式来监听。与 prop 大小写格式一样,在模板中我们也推荐使用 kebab-case 形式来编写监听器。
3、父子组件数据双向同步
如果你没有看前两个版块或者你对父传子和子传父不理解,建议你看完前两个版块再来看此版块。如果你时间充裕或者说你想彻底理解,建议前两个版块的链接点进去到vue官网仔细研读,按我提供的链接顺序相信你能彻底理解,除非你不懂vue。。。
如果你理解了子传父的机制,那么接下来的内容你会发现父子互传的秘密就在 v-model
中,那么废话不多说跟着我来浅浅深入一下v-model。
3.1 了解 v-vodel 原理
v-model
可以在组件上使用以实现双向绑定。
首先让我们回忆一下 v-model
在原生元素上的用法:
<input v-model="searchText" />
在代码背后,模板编译器会对 v-model
进行更冗长的等价展开。因此上面的代码其实等价于下面这段:
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
:value
:input输入框中输入的内容,将输入内容绑定到 ‘searchText’ 中
@input
:触发输入事件
$event
:事件对象。$event.target:事件属性返回触发事件的元素。个人理解就是<input></input> 输入框标签,通过
.value
的方式拿出输入框中的值,所以
@input="searchText = $event.target.value"
:的意思就是当你在输入框输入的时候更新绑定到 searchText 中的值。
3.2 实现父子组件数据双向同步
而当使用在一个组件上时,v-model
会被展开为如下的形式:
<CustomInput
:model-value="searchText"
@update:model-value="newValue => searchText = newValue"
/>
: model-value
:将父组件中的searchText属性的值传递给CustomInput组件,作为该组件的显示值。
@ update:model-value
:当CustomInput组件的显示值发生更新时,触发update:model-value
事件,并将更新后的值传递给newValue => searchText = newValue
函数进行处理。
newValue => searchText = newValue
:这是一个箭头函数,用于更新父组件中的searchText属性的值,将更新后的值赋给searchText变量。
这里是相应的代码CustomInput.vue
:
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
const props = defineProps(['modelValue'])
//声明事件 :update:modelValue
const emit = defineEmits(['update:modelValue'])
</script>
:value="modelValue"
:将data中的modelValue赋给input的value属性,用于设置input的初始值。
@input="$emit('update:modelValue', $event.target.value)"
:在input变化时,触发update:modelValue
:事件,并将input的值作为参数传递给父组件。这样,父组件就可以通过监听update:modelValue
:事件来更新modelValue的值。
注:在实际开发中父子组件不仅仅会通过输入框来实现数据双向同步,如果前两个版块你仔细看的话就会明白我们在js代码处可以用以下代码向告诉父组件触发的事件事件名及携带的参数:
emit('update:modelValue','要向父组件同步的数据')
父组件App.vue
:
<template>
<CustomInput v-model="searchText" />
{{searchText}}
</template>
<script setup>
import CustomInput from './CustomInput.vue'
import {ref} from 'vue'
const searchText = ref('');
</script>
而在上边我们已经展示过在组件中v-model
实际展开为:
<CustomInput
:model-value="searchText"
@update:model-value="newValue => searchText = newValue"
/>
我有必要提醒一点的是我们在子组件中声明的事件是’update:modelValue’,而在组件中使用v-model
时其中包含的事件就是 @update:model-value
,所以我们要实现父子组件数据双向同步时子组件中必须声明事件为:'update:modelValue'
。
以上两个组件就实现了父子组件数据双向同步,我们可以在父组件中更改searchText
的值和在输入框中输入值来直观看出父子组件已经实现了数据的双向绑定。
3.3 声明自定义事件实现数据双向同步
当然我们如果就想通过自定义属性来实现数据绑定的的话,我们还可以来点花活,但是我们始终绕不过v-model
。
默认情况下,v-model
在组件上都是使用 modelValue
作为 prop,并以 update:modelValue
作为对应的事件。我们可以通过给 v-model
指定一个参数来更改这些名字:
<MyComponent v-model:title="bookTitle" />
在这个例子中,子组件应声明一个 title
prop,并通过触发 update:title
事件更新父组件值:
子组件MyComponent.vue
:
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
<script setup>
const props = defineProps(['title'])
const emit = defineEmits(['update:title'])
</script>
父组件App.vue
<template>
<MyComponent v-model:title="bookTitle" />
{{bookTitle}}
</template>
<script setup>
import MyComponent from './MyComponent.vue'
import {ref} from 'vue'
const bookTitle = ref('');
</script>
值得注意的是,我们只是更改了v-model
中的prop而不是v-model
内自带的监听的事件@update:model-value
,所以我们在子组件中声明事件的时候事件名的前边总是要加上 “update:”。
以防你因为某种不知名情况下没注意上个小版块的提示,在这里我再次重申一遍:在实际开发中父子组件不仅仅会通过输入框来实现数据双向同步,如果前两个版块你仔细看的话就会明白我们在js代码处可以用以下代码向告诉父组件触发的事件事件名及携带的参数:
emit('update:modelValue','要向父组件同步的数据')