一、组件基础
组件是可服用的Vue实例,带有一个名字,我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用。
1.1组件注册、使用及其数据传递
Vue.component(name,optiosn)可用于注册组件
范例:提取food新增和食物列表组件
<!-- 列表组件 -->
<food-list :foods="foodList"></food-list>
<script>
//食物列表组件
Vue.component("food-list", {
data() {
return {
selectedFood: "",
};
},
props: {
foods: {
type: Array,
default: [],
},
},
template: `
<div>
<!-- 条件渲染 -->
<p v-if="foods.length == 0">没有任何数据</p>
<div v-else>
<div v-for="item in foods"
:key="item"
:style="{backgroundColor:selectedFood == item ? '#ddd' : 'transparent' }"
@click="selectedFood = item"
>
{{item}}
</div>
</div>
</div>
`,
});
</script>
1.2自定义事件及其监听
当子组件需要和父组件进行通信,可以派发并监听自定义事件。
范例:新增课程组件派发事件
<!-- 新增数据 -->
<!-- 自定义事件及其监听 -->
<!--
当子组件需要和父级组件进行通信,可以派发并监听自定义事件。
-->
<food-add @add-food="addFood"></food-add>
<script>
//食物新增组件
Vue.component("food-add", {
data() {
return {
food: "",
};
},
template: `
<div>
<!--用户输入 -->
<p>
<input v-model="food" type="text" v-on:keydown.enter="addCourse" />
<button v-on:click="addFood">新增</button>
</p>
</div>
`,
methods: {
addFood() {
//派发事件,通知父组件新增数据
//自定义事件的名称,不建议驼峰,建议羊肉串
this.$emit("add-food", this.food);
this.food = "";
},
},
});
const app = new Vue({
el: "#app",
data() {
return {
foodList: ["萝卜", "白菜"],
};
},
methods: {
addFood(food) {
console.log("food", food);
this.foodList.push(food);
},
},
});
</script>
1.3在组件上使用v-model
范例:改造food-add为支持v-model的版本
<!--
在组件上使用v-model
<food-add v-model="food" @add-food = "addFood"></food-add>
-->
<!--
在自定义组件使用双向数据绑定
在开发中,可能有这样的需求
food这个变量确实不想放在子组件中去维护
food可能和其他各种各样的数据,都在在父组件中一起去维护的
父组件只是把food这个变量交给子组件去维护
改变之后,最终使用的还是父组件
-->
<!--
语法糖
默认情况下:
v-model="food"
会变成
:value="food"
@input="food=$event"
$event是固定的名字,是子组件传递出来的参数
父组件把参数赋值给food
-->
<!--
在子组件中我们要做的是,
1.派发一个默认的input事件,并且要把最新的值传递出来,作为参数
2.在子组件接收一个属性,value,但是不去改变他
-->
<food-add v-model="food" @add-food="addFood"></food-add>
<script>
//食物新增组件
Vue.component("food-add", {
//1.这个不在需要,放在父组件中
// data() {
// return {
// food: "",
// };
// },
//1.addFood中不需要维护food
//2.接收父组件传递的值 props: ["value"],
//3.input不需要v-model,改为:value="value"
//4.增加input事件
props: ["value"],
template: `
<div>
<!--用户输入 -->
<p>
<input
:value="value"
type="text"
@input="onInput"
v-on:keydown.enter="addCourse" />
<button v-on:click="addFood">新增</button>
</p>
</div>
`,
methods: {
addFood() {
//this.$emit("add-food", this.food);
//this.food="";
//以上代码中,子组件已经没有food了,就不需要维护
this.$emit("add-food");
},
//4.增加input事件
//5.在app组件的data增加food
onInput(e) {
this.$emit("input", e.target.value);
},
},
});
const app = new Vue({
el: "#app",
data() {
return {
food: "", //还原food
foodList: ["萝卜", "白菜"],
};
},
methods: {
// addFood(food) {
// console.log("food", food);
// this.foodList.push(food);
// },
addFood() { //还原addFood
//food称为父组件的当前的data,不需要接参数了
this.foodList.push(this.food);
this.food = "";
},
},
});
</script>
v-mode默认转换是:value和@input,如果想要修改这种行为,可以通过model选项
Vue.component('food-add',{
model:{
prop:'value',
event:'change'
}
});
1.4通过插槽分发内容
通过使用vue提供的<slot>
元素可以给组件传递内容
范例:弹窗组件
<style>
.active {
background-color: #ddd;
}
.message-box {
padding: 10px 20px;
background: #4fc08d;
border: 1px solid #42b983;
}
.message-box-colse {
float: right;
cursor: pointer;
}
</style>
<!-- 弹窗组件 -->
<!--
:show="true"
show前面加冒号,等号后面是boolean
show前面不加冒号,等号后面是string
-->
<message :show="isShow" @close="isShow = $event "> 新增成功! </message>
<script>
//弹窗组件
/*
<span class="message-box-colse" @click="$emit('close',false)">X</span>
在关闭按钮这里增加一个自定义组件的派发
methods: {
addFood() {
this.$emit("add-food",true);
},
onInput(e) {
this.$emit("input", e.target.value);
},
},
直接写表达式,这样写,就不用写methods
@click = "$emit("close",false)"
*/
Vue.component("message", {
props: ["show"], //使用props弹窗是否展示是由父组件决定的
template: `
<div class="message-box" v-if="show">
<!--通过slot获取父组件传入的内容-->
<slot></slot>
<span class="message-box-colse" @click="$emit('close',false)">X</span>
</div>
`,
});
const app = new Vue({
el: "#app",
data() {
return {
isShow: false,
};
},
methods: {
addFood(show) {
//显示弹窗
this.isShow = show;
},
},
});
</script>
用.sync改进
<!--
@close="isShow = $event"
父组件的 isShow = 子组件传递出来的参数 $event
语法上显得很啰嗦,2.4版本新增了 .sync 修饰符
1. @close="isShow = $event" 去掉
2. :show="isShow" 改为 :show.sync="isShow" show 后面增加 .sync
3. @update:show = "" @updata:变量名 变量名就是 .sync修饰的变量的名称
父组件约定好了 子组件 自定义事件的名字
对show的控制权是当前父组件,子组件要求传自定义事件的名字就固定了
4.message子组件中
<span class="message-box-colse" @click="$emit('close',false)">X</span>
关闭事件的自定义事件名close 改为 update:show
<span class="message-box-colse" @click="$emit('update:show',false)">X</span>
-->
<message :show.sync="isShow"> 新增成功! </message>
<script>
Vue.component("message", {
props: ["show"], //使用props弹窗是否展示是由父组件决定的
template: `
<div class="message-box" v-if="show">
<!--通过slot获取父组件传入的内容-->
<slot></slot>
<!-- 用.sync需要修改自定义的事件名字为:update:show
<span class="message-box-colse" @click="$emit('close',false)">X</span>
-->
<span class="message-box-colse" @click="$emit('update:show',false)">X</span>
</div>
`,
});
</script>
具名插槽
<!--
具名插槽 :把指定内容放在指定位置
1.
-->
<message :show.sync="isShow">
<!--
具名插槽
命名为title的插槽内容
1.使用 <template></template>
2.用v-slot指令指定名字
v-slot:title
v-slot:后面的title就指定了子组件的插槽名称
3.在子组件中增加 <slot name="title"></slot>
-->
<template v-slot:title>
<strong>恭喜</strong>
</template>
<!--
默认插槽内容
不起名字 等同 v-slot:default
-->
<template> 新增成功! </template>
</message>
<script>
Vue.component("message", {
props: ["show"], //使用props弹窗是否展示是由父组件决定的
template: `
<div class="message-box" v-if="show">
<!--具名插槽-->
<slot name="title">默认标题</slot>
<!--通过slot获取父组件传入的内容-->
<slot></slot>
<!--
<span class="message-box-colse" @click="$emit('close',false)">X</span>
-->
<span class="message-box-colse" @click="$emit('update:show',false)">X</span>
</div>
`,
});
</script>
作用域插槽
<!--
作用域插槽
<template v-slot:title>
<strong>{{title}}</strong>
</template>
title获取的场所,应该是当前使用message的父组件中去查找的
应该定义的位置是根示例的这个title
const app = new Vue({
el: "#app",
data() {
return {
title: "hello vue!",
};
},
});
现在有一个特殊的需求:
title的值不是来自于当前上下文,而是来自于message的子组件,那如何来获取这个值呢?
使用的方式就是 作用域插槽
<template v-slot:title="slotProps">
<strong>{{slotProps.title}}</strong>
</template>
<slot name="title" title="来自message的标题">默认标题</slot>
title来自于哪里,决定了要不要用作用于插槽
-->
<message :show.sync="isShow">
<!-- slotProps 名字自定义 上下文-->
<template v-slot:title="slotProps">
<!-- <strong>{{title}}</strong> -->
<strong>{{slotProps.title}}</strong>
</template>
<template> 新增成功! </template>
</message>
<script>
Vue.component("message", {
props: ["show"], //使用props弹窗是否展示是由父组件决定的
template: `
<div class="message-box" v-if="show">
<!--具名插槽-->
<!--
<slot name="title">默认标题</slot>
-->
<slot name="title" title="来自message的标题">默认标题</slot>
<!--通过slot获取父组件传入的内容-->
<slot></slot>
<!--
<span class="message-box-colse" @click="$emit('close',false)">X</span>
-->
<span class="message-box-colse" @click="$emit('update:show',false)">X</span>
</div>
`,
});
</script>
二、Vue组件化的理解
2.1组件化的本质
组件化是Vue的精髓,Vue应用就是由一个个组件构成的。Vue的组件化涉及到的内容非常多,当面试时被问到:谈一下你对组件化的理解。这时候有可能无从下手。可以从以下几点阐述:
定义:组件是可复用的Vue实例,准确将他们是VueComponent的实例,继承自Vue。
优点:组件化可以增加代码的复用性、可维护性和可测试性。
**使用场景:**什么时候使用组件?以下分类可以作为参考:
- 通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等。
- 业务组件:它们完成具体业务,具有一定的复用性,例如登录组件、轮播图组件。
- 页面组件:组织应用各部分独立内容,需要时在不同页面组件切换,例如列表页、详情页组件
如何使用组件
定义:Vue.component() , components选项 , sfc
分类:有状态组件,functional , abstract
通信:props , $emit() / $on() , provide / inject , $children / $parent / $root / $attrs / $listeners
内容分发:<slot> , <template> , v-slot
使用及优化:is , keep-alive , 异步组件
组件的本质
vue中的组件经历如下过程
组件配置 => VueComponent实例 => render() => Virtual DOM => DOM
所以组件的本质是产生虚拟DOM