深入组件
Props
-
Prop 类型
-
传入一个对象的所有property
子组件的props部分怎么写????????
-
单项数据流
-
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 作为其初始值:
props: ['initialCounter'], data() { return { counter: this.initialCounter } }
-
这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
-
-
Props 验证
为了定制 prop 的验证方式,你可以为
props
中的值提供一个带有验证需求的对象,而不是一个字符串数组。app.component('my-component', { props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 }, // 带有默认值的对象 propE: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function() { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function(value) { // 力扣 – 中文网 助你高效提升编程技能 https://www.javascriptc.com/special/leetcode/ // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } }, // 具有默认值的函数 propG: { type: Function, // 与对象或数组默认值不同,这不是一个工厂函数 —— 这是一个用作默认值的函数 default: function() { return 'Default function' } } } })
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如
data
、computed
等) 在default
或validator
函数中是不可用的。??? -
Prop 的大小写命名(camelCase vs kebab-case)
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。
重申一次,如果你使用字符串模板,那么这个限制就不存在了。
非 Prop 的Attribute
-
Attribute 继承
当组件返回单个根节点时,非 prop attribute 将自动添加到根节点的 attribute 中。同样的规则适用于事件监听器。
-
禁用 Attribute 继承
如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置
inheritAttrs: false
。通过将
inheritAttrs
选项设置为false
,你可以访问组件的$attrs
property,该 property 包括组件props
和emits
property 中未包含的所有属性 (例如,class
、style
、v-on
监听器等)。 -
多个根节点上的 Attribute 继承
与单个根节点组件不同,具有多个根节点的组件不具有自动 attribute 回退行为。如果未显式绑定
$attrs
,将发出运行时警告。<custom-layout id="custom-layout" @click="changeValue"></custom-layout>
// 力扣 – 中文网 助你高效提升编程技能 https://www.javascriptc.com/special/leetcode/ // 这将发出警告 app.component('custom-layout', { template: ` <header>...</header> <main>...</main> <footer>...</footer> ` }) // 没有警告,$attrs被传递到<main>元素 app.component('custom-layout', { template: ` <header>...</header> <main v-bind="$attrs">...</main> <footer>...</footer> ` })
自定义事件
-
不同于组件和 prop,事件名不存在任何自动化的大小写转换(这里面指的是组件里面的组件名、prop和事件名)。触发的事件名需要完全匹配监听这个事件所用的名称(监听这个事件所用的名称指的是DOM模板中的事件名称)。如果我们触发一个 camelCase 名字的事件,则监听这个名字的 kebab-case 版本是不会有任何效果的。我的理解是触发的事件名如果是
this.$emit('myEvent')
,那么它不会转化成this.$emit('my-event')
,而DOM模板中监听这个事件的名称若是<my-component @my-event="doSomething"></my-component>
, 那么这个事件触发是不会生效的。(html大小写不敏感,所有大写会被转化成小写)不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且
v-on
事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以@myEvent
将会变成@myevent
——导致myEvent
不可能被监听到。因此,我们推荐你始终使用 kebab-case 的事件名。
-
当在
emits
选项中定义了原生事件 (如click
) 时,将使用组件中的事件替代原生事件侦听器。 -
与 prop 类型验证类似,如果使用对象语法而不是数组语法定义发出的事件,则可以验证它。
-
默认情况下,组件上的
v-model
使用modelValue
作为 prop 和update:modelValue
作为事件。我们可以通过向v-model
传递参数来修改这些名称。
插槽
- 插槽内容
- 字符串只是开始!插槽还可以包含任何模板代码,包括 HTML,或其他组件。
- 如果 组件 的 template 中没有包含一个
<slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
- 渲染作用域
-
当你想在一个插槽中使用数据时,例如:
<todo-button> Delete a {{ item.name }} </todo-button>
该插槽可以访问与模板其余部分相同的实例 property (即相同的“作用域”)。
-
插槽不能访问
<todo-button>
的作用域。例如,尝试访问action
将不起作用: -
<todo-button action="delete"> Clicking here will {{ action }} an item <!-- `action` 未被定义,因为它的内容是传递*到* <todo-button>,而不是*在* <todo-button>里定义的。 --> </todo-button>
-
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
-
我的理解是:自定义组件标签里面的内容不能像html元素标签一样直接渲染,它最终得借助slot插槽去显示。因此,直接在自定义组件标签里面访问父组件的property是可以的,但是不会被渲染。最终被渲染还需借助插槽。这样理解的话,插槽也算一种父子组件通信的方式了。
-
<div id="app"> <todo-button :item="item"> <!-- 下面这句代码并不会被渲染 这也应证了如果 组件 的 template 中**没有**包含一个 `<slot>` 元素, 则该组件起始标签和结束标签之间的任何内容都会被抛弃。--> Delete a {{ item }} </todo-button> {{ item }} </div> <script> const app = Vue.createApp({ data() { return { item: 'First Item!' } } }); app.component('TodoButton',{ props: ['item'], template: ` <div> {{ item }} <button class="btn-primary">Create a new item</button> </div> ` }) app.mount('#app'); </script>
-
<div id="app"> 父组件的模板:{{ item }} <todo-button> <!-- 此时可以显示item --> 父组件下的子组件标签:{{ item }} </todo-button> </div> <script> const app = Vue.createApp({ data() { return { item: 'First Item!' } } }); app.component('TodoButton',{ template: ` <div> <!-- 下面的item访问不到 --> 子组件的模板:{{ item }} <slot></slot> <button class="btn-primary">Create a new item</button> </div> ` }) app.mount('#app'); </script>
- 后备内容
- 有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。
- 具名插槽
<slot>
元素有一个特殊的 attribute:name
。这个 attribute 可以用来定义额外的插槽。在向具名插槽提供内容的时候,我们可以在一个<template>
元素上使用v-slot
指令,并以v-slot
的参数的形式提供其名称。一个不带name
的<slot>
出口会带有隐含的名字“default”。注意,v-slot
只能添加在<template>
上 (只有一种例外情况)
- 作用域插槽
-
有时让插槽内容能够访问子组件中才有的数据是很有用的。当一个组件被用来渲染一个项目数组时,这是一个常见的情况,我们希望能够自定义每个项目的渲染方式。
-
要使
item
可用于父级提供的 slot 内容,我们可以添加一个<slot>
元素并将其绑定为属性。绑定在<slot
> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的v-slot
来定义我们提供的插槽 prop 的名字。 -
<div id="app"> <todo-list> <!-- 将包含所有插槽 prop 的对象命名为 slotProps --> <template v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green"> {{ slotProps.item }} </span> </template> </todo-list> </div> <script> const app = Vue.createApp({ data() { return {} } }); app.component('todo-list', { data() { return { items: ['Feed a cat', 'Buy milk'] } }, template: ` <ul> <li v-for="(item,index) in items"> <slot :item="item"></slot> </li> </ul> ` }); app.mount('#app') </script>
-
<div id="app"> <todo-list> <!-- 将包含所有插槽 prop 的对象命名为 slotProps --> <!-- my-slot 为 v-slot 指令的参数的参数名,此时改成default不会生效 --> <template v-slot:my-slot="slotProps"> <i class="fas fa-check"></i> <span class="green"> {{ slotProps.item }} </span> </template> </todo-list> </div> <script> const app = Vue.createApp({ data() { return {} } }); app.component('todo-list', { data() { return { items: ['Feed a cat', 'Buy milk'] } }, template: ` <ul> <li v-for="(item,index) in items"> <slot name="my-slot" :item="item"></slot> </li> </ul> ` }); app.mount('#app') </script>
- 独占默认插槽的缩写语法
-
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。
-
注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确。
-
<div id="app"> <todo-list> <template v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green"> default slot: {{ slotProps.item }} </span> </template> <template v-slot:other="slotProps"> <i class="fas fa-check"></i> <span class="green"> other slot: {{ slotProps.item }} </span> </template> </todo-list> </div> <script> const app = Vue.createApp({ data() { return {} } }); app.component('todo-list', { data() { return { items: ['Feed a cat', 'Buy milk'] } }, template: ` <ul> <li v-for="(item,index) in items"> <slot name="default" :item="item"></slot> <slot name="other" :item="item"></slot> </li> </ul> ` }); app.mount('#app') </script>
-
解构插槽 Prop
作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里:
function (slotProps) { // ... 插槽内容 ... }
这意味着
v-slot
的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。你也可以使用 ES2015 (opens new window)解构来传入具体的插槽 prop。<div id="app"> <!-- v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式 --> <todo-list v-slot:default="{ item }"> <i class="fas fa-check"></i> <span class="green"> {{ item }} </span> </todo-list> </div> <script> const app = Vue.createApp({ data() { return {} } }); app.component('todo-list', { data() { return { items: ['Feed a cat', 'Buy milk'] } }, template: ` <ul> <li v-for="(item,index) in items"> <slot name="default" :item="item"></slot> </li> </ul> ` }); app.mount('#app') </script>
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将
item
重命名为todo
:<div id="app"> <!-- v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式 --> <todo-list v-slot:default="{ item: todo }"> <i class="fas fa-check"></i> <span class="green"> {{ todo }} </span> </todo-list> </div> <script> const app = Vue.createApp({ data() { return {} } }); app.component('todo-list', { data() { return { items: ['Feed a cat', 'Buy milk'] } }, template: ` <ul> <li v-for="(item,index) in items"> <slot name="default" :item="item"></slot> </li> </ul> ` }); app.mount('#app') </script>
你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形:
<div id="app"> <!-- v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式 --> <todo-list v-slot:default="{ item = 'Placeholder' }"> <i class="fas fa-check"></i> <span class="green"> {{ item }} </span> </todo-list> </div> <script> const app = Vue.createApp({ data() { return {} } }); app.component('todo-list', { data() { return { items: ['Feed a cat', 'Buy milk'] } }, template: ` <ul> <li v-for="(item,index) in items"> <slot name="default" :item="item"></slot> </li> </ul> ` }); app.mount('#app')
-
动态插槽名 ????
-
具名插槽的缩写
跟
v-on
和v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符#
。然而,和其它指令一样,该缩写只在其有参数的时候才可用。
<div id="app"> <base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <template #default> <h1>A paragraph for the main content.</h1> </template> <template #footer> <h1>Here's some contact info.</h1> </template> </base-layout> </div> <script> const app = Vue.createApp({ data() { return { } } }); app.component('BaseLayout',{ template: ` <div> <slot name="header"></slot> <slot ></slot> <slot name="footer"></slot> </div> ` }) app.mount('#app')
今日疑惑 2022/7/29
Vue 中的 模板 是什么含义?
字符串模板 与 DOM 模板各自是什么含义?区别是什么?
解构插槽 Prop 源码?
import 与 require 的区别? export、export default 与 exports、module.exports 的区别?
动态插槽名 的用法?
非 Prop 的 Attribute 能在父子组件中传值吗?
组件实例能直接在模板中暴露的内容有哪些?props、emits、data?