组件化开发
组件的概念
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。
在vue中都是组件化开发的,组件化开发就是把一个完整的页面分割成一个一个的小组件,组件包含独立的结构,样式,js。
组件的优点:
- 容易维护
- 复用
vue组件分为全局组件和局部组件:
- 全局注册的组件:全局注册的组件可以在应用中的任何组件的模板中使用。
- 局部注册的组件:局部注册的组件在其子组件中不可用
全局组件与局部组件
定义全局组件
- 通过vue实例
app.component(组件名,组件的配置项)
定义一个全局组件 - 在vue实例的内部,通过components属性来定义局部组件
- 因为组件是可复用的实例,所以它们与根实例接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。
- 注意点1. 定义的组件名不能是html原先就存在的标签。
- 注意点2:组件的模版只能有一个根元素
- 通过vue实例
使用组件:
- 把组件当成html标签来使用
<body>
<div id="app">
<!--把组件名称当成html标签来使用 -->
<com-a></com-a>
<hr>
<com-b></com-b>
</div>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script>
// 创建一个Vue 应用,根组件
const app = Vue.createApp({
// 在vue实例的内部,通过components属性来定义局部组件
components: {
'com-b': {
template: '<div><h3>我是【局部组件->com-b】</h3><sapn style="color:blue">在局部组件中引用了全局组件->com-a:[<com-a></com-a>]</sapn></div>'
}
}
})
// 通过vue实例 app.component()方法定义一个名为 com-a 的【全局组件】
app.component('com-a', {
// 如果有很多的内容,template可以使用字符串模版
template: `<div>
<h3>我是【全局组件->com-a】</h3>
<span style="color:blue">在全局组件中引用了局部组件->com-b:[<com-b></com-b>]</span>
</div>` // 在典型的 Vue 应用中,我们使用单文件组件而不是字符串模板
})
const vm = app.mount('#app')
</script>
</body>
组件是特殊的vue实例
可以将组件看成是一个vue的实例,因此,在vue实例中能配置的属性,在组件中依旧能够配置。
比如:data,method,watch,computed,钩子函数等
注意:组件中data属性必须是一个函数,返回值才是data的数据
- 可以把组件看成一个vue实例
- 组件是一个独立封闭的个体,组件之间的数据是无法相互使用的
<body>
<div id="app">
<com-a></com-a>
<com-a></com-a>
</div>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script>
// 创建一个Vue 应用,根组件
const app = Vue.createApp({})
// 通过vue实例 app.component()定义一个【全局组件】
// 可以把组件看成一个vue实例,在vue实例中能配置的属性,在组件中依旧能够配置
app.component('com-a', {
template: `
<div>
<h3>我是【全局组件】</h3>
<span>{{ respect }}</span>
<button @click="fn">点赞</button>
<hr>
</div>`,
data() {
return {respect: 100}
},
methods: {
fn() {
this.respect += 1
}
}
})
const vm = app.mount('#app')
</script>
</body>
组件通讯
因为组件是一个独立的个体,组件无法使用到外部的数据
但是在真实开发中,多个组件之间是需要相互使用彼此的数据的,因此需要使用组件通讯的技术,让组件之间能够相互传值。
组件通讯分为两类
- 父组件传递值给子组件
- 子组件传递值给父组件
组件通讯-父传子
- 子组件通过一个属性: props属性 接收父组件传递过来的数据
- 父组件给子组件传递数据:给子组件添加属性即可
<body>
<div id="app">
<!-- son组件在根组件中渲染了, son组件和根组件就形成了父子关系 -->
<!-- 父组件给子组件传递数据 -->
<son class="son" :car="car" :money="money"></son>
</div>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script>
// 根组件
const app = Vue.createApp({
data() {
return {
car: 'BMW',
money: 1000000
}
}
})
// son组件
app.component('son', {
// props用于接收父组件传递过来的数据
// props和data一样,都可以给组件提供数据
// data是组件自己的私有数据 props是父组件传递过来的数据
props: ['money', 'car'],
// 子组件使用父组件传递过来的值
template: `
<div style="background: pink;border: red solid 1px">
<h3>我是son组件</h3>
<p>{{ car }}-----{{ money }}</p>
</div>`
})
const vm = app.mount('#app')
</script>
</body>
- 父传子-总结:
- 子组件通过一个属性: props属性 可以接受父组件传递过来的数据
props:['money', 'car']
- 父组件需要给子组件传值 只需要给子组件身上增加自定义的属性
<son money="100" :car="car"></son>
- 区分props和data
- 组件的数据可以是data中,也可以是props中
- data是组件自己的数据 props是父组件传过来的数据
- data中的数据允许修改,但是props中的数据不允许修改(props是只读的)
注意:props负责获取父组件传递过来的数据,props中的值是只读的,不允许修改
组件通讯-子到父
整体思路
- 父组件给子组件注册一个自定义事件,监听子组件事件
- 子组件触发这个自定义事件,触发事件时把数据传递给父组件
<body>
<div id="app">
<h3> 我是父组件:{{name}}----{{price}}</h3>
<!-- son组件在根组件中渲染了, son组件和根组件就形成了父子关系 -->
<!--在子组件上,自定义监听事件buycar,等待子组件触发,然后父组件执行响应监听函数getcar-->
<son @buycar="getcar"></son>
</div>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script>
// 根组件
const app = Vue.createApp({
data() {
return {
name: '',
price: ''
}
},
methods:{
// 父组件监听到子组件事件后,执行相应函数,在这里获取从子组件中传过来的值。
getcar(car){
this.name = car.name
this.price = car.price
}
}
})
// son组件
app.component('son', {
template: `
<div style="background: pink;border: red solid 1px">
<h3>我是son组件</h3>
<button @click="toParent"> 给老父亲买辆车</button>
</div>`,
methods:{
toParent(){
// 触发父组件所监听的事件buycar,传上相应的值
this.$emit('buycar',{name:'奥迪Q7',price:'50w'})
}
}
})
const vm = app.mount('#app')
</script>
</body>
子传父-总结:
- 父组件给子组件注册一个自定义监听事件
<son @buycar="getcar"></son>
- 子组件去触发父组件所监听的事件,并且传值
this.$emit('buycar',{name:'奥迪Q7',price:'50w'})
- 父组件监听到子组件事件后,执行指定的监听函数,在函数内获得子组件传过来的值
案例-todomvc
slot插槽
当组件中某一项需要单独定义,那么就应该使用solt
插槽:内容分发,会把组件中的内容分发到slot中
- 默认插槽,匿名插槽:
在组件占位置 - 具名插槽, 给插槽提供一个名字
如果是具名插槽, 给具名插槽传内容,内容需要包裹在一个template标签中 - 作用域插槽: 带有数据的插槽
单个slot
除非子组件模板包含至少一个
<slot>
插口,否则父组件的内容将会被丢弃 ,当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。
在组件的模版中定义slot
插槽
app.component('my-modal', {
template: `
<div class="modal">
<div class="header">
<h3>温馨提示</h3>
</div>
<div class="content">
<slot></slot>
</div>
<div class="footer">
<button>确定</button>
<button>取消</button>
</div>
</div>
`
})
父组件传值
<my-modal>你确定要退出系统吗?</my-modal>
<my-modal>你确定要删除这个内容吗?</my-modal>
具名插槽
如果一个组件中想使用多个slot那么此时就应该使用具名slot。
在组件的模版中定义slot
插槽
app.component('my-modal', {
template: `
<div class="modal">
<div class="header">
<slot name="header"></slot>
</div>
<div class="content">
<slot name="content"></slot>
</div>
<div class="footer">
<slot name="footer">
<button>确定</button>
<button>取消</button>
</slot>
</div>
</div>
`
})
父组件传值
给具名插槽传内容,内容需要包裹在一个template标签中, 通过
v-slot:插槽名
或#插槽名
指定插槽
<my-modal>
<template v-slot:header>
<h3>温馨提示</h3>
</template>
<template v-slot:content>
<p>你确定要删除吗?</p>
</template>
</my-modal>
<my-modal>
<template #header>
<h3>警告</h3>
</template>
<template #content>
<p>你确定要删除吗?</p>
</template>
<template #footer>
<a href="#">确定</a>
</template>
</my-modal>
作用域插槽
场景: 在父组件中获取插槽上的值
<body>
<div id="app">
<todo-list :todos="todos_list">
<template #delete="scope">
<button @click="del(scope.todo_id)">删除</button>
</template>
</todo-list>
</div>
<script src="./node_modules/vue/dist/vue.global.js"></script>
<script>
// 创建一个Vue 应用,根组件
const app = Vue.createApp({
data() {
return {
todos_list: [
{id: 1, name: '吃饭', flag: true},
{id: 2, name: '喝酒', flag: false},
{id: 3, name: '唱歌', flag: false}
]
}
},
methods: {
del(id) {
this.todos_list = this.todos_list.filter(item => item.id != id)
}
}
})
// 通过vue实例 app.component()定义一个【全局组件】
app.component('todo-list', {
template: `
<ul>
<li v-for="item in todos" :key="item.id"><span>{{ item.name }}</span>
<slot name="delete" :todo_id="item.id"></slot>
</li>
</ul>
`,
props: ['todos']
})
const vm = app.mount('#app')
</script>
</body>
解析:
- 在组件模板中,插槽定义
<slot name="delete" :todo_id="item.id"></slot>
中动态绑定属性todo_id
- 在父组件中,插入插槽内容时 ,给插槽添加一个作用域对象,如
#delete="scope"
,scope是一个对象(名称自定义,可以不是scope),这个对象中会包含插槽中所有的数据
在这个例子中,scope.todo_id
就可以获取对应点击的todo_id
vue学习总结
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。