课程名称: 2022持续升级 Vue3 从入门到实战 掌握完整知识体系
课程章节: 探索组件的理念
主讲老师: Dell
课程内容:
今天学习的内容包括:
组件:根组件在实例里面写,子组件在实际例外面通过components创建。
组件具备复用性;
组件分为:全局组件和局部组
课程收获:
3.1/3.2 心得:
全局组件不用时也会挂载在APP上 ,处处可以调用,性能不高,使用简单
-
全局组件:只要定义了,处处可以使用(子组件使用子组件,父组件使用子组件),性能不高,但是用起来简单;
名字建议: 小写字母单词,多个字母中间用横线分隔例如hello-world
-
局部组件: 定义了,要在Vue.createApp({})中用components{}注册才能使用,性能比较高,使用起来有些麻烦;
名字建议: 驼峰式命名;例如HelloWorld;单个单词首字母大写,以便区分局部组件和普通常量
注意: 局部组件使用时,要做一个名字和组件间的映射对象,如果组件用的名字和定义的名子相同,不做映射,Vue底层也会自动尝试做映射,否则必须做映射。
components:{ counter:Counter }
3.4 心得:
组件间传值:静态传值和动态传值
静态传值:
- 组件上写传值名称和要传的值,例如 content=“123”;
- 子组件通过props接收, 例如:props: [‘content’].
- 简单但有限制,只能传来string类型
动态传值:
- 要在传值名称前加v-bind,传的值是个变量; 例如:v-bind:content=“message”(message变量在data中定义)
- 子组件通过props接收。
- 灵活且种类不限制。 String,Boolean,Array, Object, Function, Symbol都可以。
传值校验
- 只种类校验的话在 props: { content: Number } 直接写想要传的类型,如果传的不对页面依旧展示但是会警告
列表项 - type:校验类型
- default: 默认值(没有传值时,展示默认值,可以是函数)function() { … }
- required: bool值—true:必须有值
- validator: function(value) { … } 深度校验(value是传来的值)是否符合想要的特性
例如:
props: {
content: {
type: ...,
required: ...,
default: ...,
validator: function(value) { ... }
}
}
props接受父组件传值,可以用数组和对象来接收,在对象里面,接收数据的类型可以是:Number String Boolean Array Object Function Symbol;
props接收的值也可以写的更加详细,这个值可以用一个对象来接收,写明具体的类型,是否必须传指定类型;
3.5 心得:
组件间传值传递多个值
前置条件: data中有 a, b, c等多个数据; 有个子组件为 demo
简写前:要传递多个值时,需要挨个写要传递属性 ,例如 <demo :a=“a” :b=“b” :c=“c” … />
简写后:可以将要传递的多个值放到一个对象里,例如:obj: { a: 123, b: 234, c: 345 };然后传递属性时只需要 v-bind=“obj”, 在子组件的props中接收[‘a’, ‘b’, ‘c’] ;例如 , 相当于 <demo :a=“obj.a” :b=“obj.b” :c=“obj.c” … />
在 html 标签上的属性名称太长
建议使用短横线(-)来连接单词(例如:),vue标签是不支持驼峰式的语法的。
注意:子组件接收时要用驼峰式,(例如props:[‘dataTime’])
单向数据流:
子组件可以使用父组件传过来的数据,但不能修改该数据。因为如果可以修改,在其他地方引用该数据也会因为改变而改变,会引起逻辑错误和混乱
如果要改变,可以自定义一个变量,让它的值等于传过来的值,修改自定义的值
父组件向子组件传多个值时简写:v-bind=“params”
// 父组件
data(){
return {
params:{
content:1234,
a:123,
b:456,
c:789
}
}
},
template:`<div><test v-bind="params" /></div>`
属性传的时候,使用 content-abc 这种命名,接的时候,使用contentAbc 命名
3.6 心得:
Non-prop属性:父组件向子组件传递内容,子组件没有用props来接收 ( 可适用于样式传递 )。Vue底层会把父组件传递给子组件的内容放在子组件最外层的dom标签上。
- 该情况适用于子组件只有一个根节点
- 当子组件有多个根节点时,父组件的Non-prop属性就不会生效
// 父组件
const app = Vue.createApp({
template:`<div><counter ></div> `
})
// ① 子组件(子组件只有一个根节点)
app.component( 'counter', {
template: ` <div> Counter </div> `
} );
// 浏览器 Counter 显示为红色字体
<div > Counter </div>
// ② 子组件(子组件有多个根节点)
app.component( 'counter', {
template: `
<div> Counter1 </div>
<div> Counter2 </div>
<div> Counter3 </div>
`
} );
// 浏览器 Counter无任何样式
<div> Counter1 </div>
<div> Counter2 </div>
<div> Counter3 </div>
子组件不继承父组件的父组件的Non-prop属性 (子组件只有一个根节点)
当子组件设置inheritAttrs:false时,就可以让子组件不继承父组件的Non-prop属性。
app.component( 'counter', {
inheritAttrs: false,
template: ` <div> Counter </div> `
} );
// 浏览器开发者工具 元素上显示
<div> Counter </div>
子组件选择性接收父组件的属性 (子组件有多个根节点)
- 在子组件的根节点上使用 v-bind="$attrs" 可接收所有父组件传过来的内容,
- 使用 :msg="$attrs.msg" 可以接收指定的内容。
- 在子组件内也可以用 this.$attrs 来获取父组件 Non-prop 的属性。
// 父组件
const app = Vue.createApp({
template:`<div><counter msg="hello" msg1="world"></div> `
})
// 子组件 第一种情况
app.component( 'counter', {
mounted() {
console.log( this.$attrs ); // 第三种情况
},
template: `
<div v-bind="$attrs"> Counter1 </div> // 第一种情况
<div :msg="$attrs.msg"> Counter2 </div> // 第二种情况
`
} );
// 浏览器 Counter无任何样式
<div msg="hello" msg1="world"> Counter1 </div>
<div msg="hello" > Counter2 </div>
传递所有的参数从父组件到子组件:v-bind:params
传递所有的属性到子组件中的某一个节点: v-bind:’$attrs’
3.7/3.8 心得:
组件间通过事件通信:
- 子组件通过 this.$emit() 来向外触发事件,可带参数(事件,参数1, … )
- 传递的方法是驼峰式命名,接收用“-”分隔, 并用@监听
例如子组件方法里用 this.$emit(‘addOne’) 向父组件传递,父组件通过@add-one="handleAddOne"接收, 传递参数的时候,父组件方法里可以接收
子组件字段 emits
- 可以是数组 [ ], 也可以是对象 { } ;
- 书写要向父组件传递的事件方法名称,以便清楚知道都传了哪些事件,不容易混乱。
- {}里可以写方法,对事件触发的参数进行校验,是否满足自己的需求,不满足发警告
// 父组件
data() {
return { count: 1 }
},
methods: {
handleAddOne( param ) {
this.count += param
}
},
template: `
<div>
<counter :count="count" @add-one="handleAddOne" />
</div>
`
// 子组件 counter
props: [ 'count' ],
// emits: [ 'addOne' ], // 数组
emits: { // 对象
addOne: ( count ) => { // 对传递的参数进行判断是否符合预期
if( count > 0 ) return true ;
return false ;
}
},
methods: {
handleClick () {
this.$emit( 'addOne', 1 );
}
},
template: `
<div @click="handleClick" > {{ count }} </div>
`
modelValue:
使用条件:
- 父组件的数据通过 “v-model” 传递给子组件
- 子组件接收的props名字必须是 “modelValue”
- 子组件向外传递事件的名字必须是 “update:modelValue”
如果不想使用 “modelValue” ,那么可以在父组件传递数据时写成 “v-model:name”,子组件在接收props时也应该用的是 “name” ,子组件向外传递事件的名字也要变成 "update:name"就可以了。
若子组件只是改变父组件传来的值,emit 方法类似于 v-model 的方式
vue 中可以将这种简化利用 v-model 来实现
// 父组件
data() {
return { count: 1 }
},
// template: ` <counter v-model="count" /> `
// 若想将接收的参数改成自定义名字 则 v-model:count count为自定义的名字
template: ` <counter v-model:count="count" /> `
// 子组件
// props: [ 'modelValue' ], // 接收参数的名称固定为 modelValue,不可以为其他名称
props: [ 'count' ], // 在这里就可以接收成count
methods: {
handleClick() {
// 事件 update: modelValue 为固定写法
// this.$emit( 'update: modelValue', this.modelValue + 3 ) ;
this.$emit( 'update: count', this.count + 3 )
}
},
// template: ` <div @click="handleClick"> {{ modelValue }} </div> `
template: ` <div @click="handleClick"> {{ count }} </div> `
3.9 心得:
若需要使用多个v-model来传递数据,需要使用 v-model:name 的形式。
// 父组件
<Counter v-model:count1="count" v-model:count2="count2" />、
// 子组件
props: [ 'count1', 'count2' ]
在非form表单元素上使用v-model后的自定义的修饰符
注: v-model 仅限在 form 表单里加系统自带的修饰符
// 父组件 v-model 加自定义修饰符 uppercase
<Counter v-model.uppercase="count" />
// 子组件 modelModifilers :指的是传递过来的修饰符, 名称是默认的不可更改
props: {
modelValue: String,
modelModifiers: {
default: () => {} // 若没有传递修饰符,则modelModifiers 为空对象
}
},
// 在该示例中 modelModifiers为 { uppercase:true }
methods: {
handleClick () {
let newValue = this.modelValue + 'b'
if (this.modelModifiers.uppercase) { // 可以结合修饰符来将参数做处理
newValue = newValue.toUpperCase()
}
this.$emit('update:modelValue', newValue)
}
}
若需要使用多个v-model来传递数据,需要使用 v-model:childName=“parentName” 的形式。
<Counter v-model:count="count" v-model:count2="count2" />
3.10/3.11 心得:
slot 插槽
- 放在调用子组件内部的东西 ,
- 插槽内可以包含任何模板代码,包括 HTML、甚至其它的组件
- 如果子组件的 template 中没有包含一个 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
- slot插槽是不能绑定事件的,如果需要绑定事件可以i在slot的外层加个标签进行事件绑定。
示例中 myform(子组件) 标签中的 提交 就是要插在子组件中的东西
// 父组件
template:`<myform> <button> 提交 </button> </myform>`
// 子组件 myform
template: `
<div>
<input />
<solt></solt>
</div>
`
// 浏览器界面显示:一个 input 输入框 , 一个提交的 button 按钮
slot的数据使用,作用域问题
在父模板里使用数据就调用父模板里的数据
在子模板里使用数据就调用子模板里的数据
父组件如果想向子组件传递一些dom节点,或者元素标签(字符串、子组件都可以传递)的时候,直接把元素写在标签中就可以,子组件调用这些标签用slot就可以
具名插槽: 将一个slot拆成多个具名插槽,使标签、dom的传递更加灵活
<layout>
<template v-slot:header>header</template> <!-- 具名插槽指定标签类型不能渲染 -->
<template v-slot:footer>footer</template>
</layout>
app.component('layout', {
template: `
<div>
<slot name="header"></slot>
<div>content</div>
<slot name="footer"></slot>
</div>
`
}
<slot></slot>
插槽用来替换父组件当中的内容
slot 作用域:父模板的数据调用父组件里的 data,子模板的数据调用子组件里的 data。
插槽中间可以添加 default value;
3.12 心得:
具名插槽的简写方法 在template标签中 #插槽名
<template #header>
<div>header</div>
</template>
vue:插槽
父组件向子组件传递dom元素时,不用props传递,用插槽实现,子组件用接收父组件传递的dom内容;
传递动态dom内容<div>{{text}}</div> <slot>default value</slot>
默认值设定
<layout>
<template v-slot:header #header>
<div>header</div>
</template>
<template v-slot:footer #footer>
<div>footer</div>
</template>
</layout>
app.component('layout',{
template: `
<div>
<slot name="header"></slot>
<div>content</div>
<slot name="footer"></slot>
</div>
`
})
作用域插槽:父组件调子组件的时候传slot进来,子组件再通过属性的形式,把数据传给子组件,父组件拿到内容时再显示
当子组件渲染的内容要由父组件决定的时候,可以使用作用域插槽,可以把父组件的item数据从子组件传递过来,父组件从理论上就可以调用子组件的数据
<list v-slot="slotProps">
<span>{{slotProps.item}}</span>
</list>
<list v-slot="{item}">
<span>{{item}}</span>
</list>
app.component('list', {
data() {
return { list: [1,2,3]}
},
template: `<slot v-for="item in list" :item="item"></slot>`
})
3.13 心得:
动态组件:根据数据的变化,结合 component 这个标签,来随时动态切换组件的显示 (component+:is)
keep-alive:缓存作用
keep-alive 动态组件缓存,当动态组件input框中输入内容后,点击切换默认是清空内容的,当需要保存输入的内容时 可以将动态组件用 keep-alive 标签包裹
<keep-alive>
<component :is=‘currentItem’ />
</keep-alive>
异步组件:异步执行某些组件的逻辑 Vue.defineAsyncComponent(()=> { return new Promise((res,rej)=>{})})
slot无法直接绑定标签
父模板里调用的数据属性,使用的都是父模板里的数据
子模板里调用的数据属性,使用的都是子模板里的数据
在标签中输入内容,是slot的默认值
具名插槽:将一个slot拆成多个具有插槽,是标签、dom的传递更加灵活
使用具名插槽v-slot要配合标签使用,不能在dom标签上使用
v-slot: 可以简写为#
作用于插槽,可以使父组件使用子组件的数据
app.component('async-common-item', Vue.defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
template: `<div>this is an async component</div>`
})
}, 5000)
})
}))
3.14 心得:
v-once:让某个元素标签只渲染一次
ref:实际上是获取Dom节点/组件引用的一个语法 (在标签上用ref绑定,获取时为this.$refs.xxx)
ref也可以在父组件上获取子组件的方法
provide、inject:多层组件传值
provide想传递给其他组件自己的data值需要变成函数,但是只能传递一次,当其自身的data值改变时,不会再传给其余组件
provide提供的数据是一次性的,不是响应式的,不是双向绑定的,inject拿到的永远是第一次传的值
// 父组件向孙子组件传递参数 provide写法
// 需要注意的是,这里传递的值不是响应式的,始终只是第一次传递过去的值。例第一次传递了数字1,就算父组件里面的方法改变了 num 的数值,孙子组件里面还是显示1
provide{
count: 1
}
// 如果 provide 想传递 data 里面的变量
data(){ return { num: 1} },
provide(){
return {
count: this.num
}
}
// 孙子组件
inject: ['count']