Vue 组件
组件是 可复用的 Vue 实例,封装标签,样式和 JS 代码。
- 组件化 :封装的思想,把页面上
可重用的部分
封装为组件
,从而方便项目的 开发 和 维护。
简单理解:一个页面, 可以拆分成一个个组件,一个组件就是一个整体,每个组件可以有自己独立的 结构 样式 和 行为 (html, css和js)
组件的创建使用
创建组件:创建.vue文件 – 标签 – 样式 – JS进去
注册组件:(全局 / 局部)
使用组件:(组件名用作标签)
执行结果:把组件标签最终替换成封装组件内的标签。
-
创建组件 (components 下的 Pannel.vue)
<template> <div> <div> <div class="title"> <h4>芙蓉楼送辛渐</h4> <span class="btn" @click="isShow = !isShow"> {{ isShow ? '收起' : '展开' }} </span> </div> <div class="container" v-show="isShow"> <p>寒雨连江夜入吴, </p> <p>平明送客楚山孤。</p> <p>洛阳亲友如相问,</p> <p>一片冰心在玉壶。</p> </div> </div> </div> </template> <script> export default { // 数据 data () { return { isShow: false } } } </script> <style lang="less" scoped> .title { display: flex; justify-content: space-between; align-items: center; border: 1px solid #ccc; padding: 0 1em; } .title h4 { line-height: 2; margin: 0; } .container { border: 1px solid #ccc; padding: 0 1em; } .btn { /* 鼠标改成手的形状 */ cursor: pointer; } </style>
-
局部注册组件,并使用 (App.vue)
<template> <div id="app"> <h3>案例:折叠面板</h3> <!-- 3.使用组件 --> <pannel></pannel> <pannel></pannel> <pannel></pannel> </div> </template> <script> // 2. 注册组件 import Pannel from './components/Pannel' export default { components: { Pannel }, } </script> <style lang="less" scoped> body { background-color: #ccc; #app { width: 400px; margin: 20px auto; background-color: #fff; border: 4px solid blueviolet; border-radius: 1em; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5); padding: 1em 2em 2em; h3 { text-align: center; } } } </style>
Vue 组件通信
App.vue
<template>
<div>
<!-- 传入数据 -->
<MyProduct title="俺是标题" price="100" info="俺是描述,这玩意儿真不错"></MyProduct>
</div>
</template>
<script>
// 引入子组件
import MyProduct from './components/MyProduct.vue'
export default {
// 注册组件
components: {
MyProduct,
},
};
</script>
MyProduct.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ info }}</p>
</div>
</template>
<script>
export default {
props: ["title", "price", "info"],
};
</script>
<style scoped>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
父向子 - props
指定类型,是否必传
props: {
title: {
typeof: String,
require: true
},
}
父向子 - 组件循环
App.vue
<template>
<div>
<MyProduct v-for="v in list"
:title="v.proname"
:price="v.proprice"
:info="v.info"
:key="v.id">
</MyProduct>
</div>
</template>
<script>
import MyProduct from './components/MyProduct.vue'
export default {
// 数据
data () {
return {
list: [
{ id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: '开业大酬宾, 全场8折' },
{ id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: '好吃不腻, 快来买啊' },
{ id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: '炎热的夏天, 来个冰激凌了' },
],
}
},
// 注册组件
components: {
MyProduct,
}
}
</script>
MyProduct.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ info }}</p>
</div>
</template>
<script>
export default {
props: ["title", "price", "info"],
};
</script>
<style scoped>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
子向父
单项数据流
简单理解:父组件向子组件发送事件,子组件触发。
触发父组件的自定义方法
this.$emit("", 参数1, 参数2);
EventBus(兄弟通信)
跨组件通信使用。
在监听方(List)接收,发送方(MyProduct)发送。
-
在
App.vue
父组件中写主体代码<template> <div> <div style="float: left;"> <Product v-for="(v, i) in list" :key="v.id" :title="v.proname" :price="v.proprice" :info="v.info" :index="i"> </Product> </div> <div style="float: left;"> <List :arr="list"></List> </div> </div> </template> <script> // 1.导入组件 import Product from './components/MyProduct.vue' import List from './components/List.vue' export default { // 2.注册组件 components: { Product, List }, // 数据 data () { return { price: 1, list: [ {id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: "开业大酬宾, 全场8折",}, {id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: "好吃不腻, 快来买啊",}, {id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: "炎热的夏天, 来个冰激凌了",}, ], } }, } </script>
-
在
MyProduct.vue
外部组件中使用<template> <div class="my-product"> <h3>标题: {{ title }}</h3> <p>价格: {{ price }}元</p> <p>{{ info }}</p> <button @click="subFn">宝刀-砍1元</button> </div> </template> <script> import eventBus from '../EventBus/index.js' export default { props: ["title", "price", "info", "index"], methods: { subFn () { // 发送 eventBus.$emit('subPrice', this.index, 1); } } }; </script> <style scoped> .my-product { width: 400px; padding: 20px; border: 2px solid #000; border-radius: 5px; margin: 10px; } </style>
-
在 src 下创建
EventBus
文件夹,下新建index.js
,并创建空白 Vue 对象并导出 (只负责监听和触发)import Vue from 'vue' export default new Vue();
-
在 components 文件夹下新建 List.vue
箭头函数没有 this,可以直接指向
<template> <div> <ul class="my-product"> <li v-for="(item, index) in arr" :key="index"> <span>{{ item.proname }}</span> <span>{{ item.proprice }}</span> </li> </ul> </div> </template> <script> import eventBus from "../EventBus/index.js"; export default { props: ["arr"], // 生命周期 函数 vue创建完毕以后调用created // created :处于loading结束后,还做一些初始化,实现函数自执行(data数据已经初始化,但是DOM结构渲染完成,组件没有加载) // mounted :处于发起后端请求,获取数据,配合路由钩子执行操作(DOM渲染完成,组件挂载完成 ) created() { // 接收(监听) eventBus.$on("subPrice", (index, price) => { if (this.arr[index].proprice > 1) { this.arr[index].proprice = (this.arr[index].proprice - price).toFixed(2); } }); }, }; </script> <style scoped> .my-product { width: 400px; padding: 20px; border: 2px solid #000; border-radius: 5px; margin: 10px; } </style>
传数据注意事项
数据在父组件中,修改父组件数据时
- 如果数据类型是一般类型,要在父组件中修改
- 如果数据类型的复杂类型,可以直接在自组件中修改
因为指针问题,复杂类型访问的是指针。不管是什么类型的数据,都推荐在父组件中修改数据。
案例:Todo 备忘录
-
创建工程和组件
创建组件 todoHeader.vue、todoMain.vue、todoFooter.vue
todoHeader.vue
<template> <header class="header"> <h1>todos</h1> <input id="toggle-all" class="toggle-all" type="checkbox" > <label for="toggle-all"></label> <input class="new-todo" placeholder="输入任务名称-回车确认" autofocus /> </header> </template> <script> export default { } </script>
todoMain.vue
<template> <ul class="todo-list"> <!-- completed: 完成的类名 --> <li class="completed" > <div class="view"> <input class="toggle" type="checkbox" /> <label>任务名</label> <button class="destroy"></button> </div> </li> </ul> </template> <script> export default { } </script>
todoFooter.vue
<template> <footer class="footer"> <span class="todo-count">剩余<strong>数量值</strong></span> <ul class="filters"> <li> <a class="selected" href="javascript:;" >全部</a> </li> <li> <a href="javascript:;">未完成</a> </li> <li> <a href="javascript:;" >已完成</a> </li> </ul> <button class="clear-completed" >清除已完成</button> </footer> </template> <script> export default { } </script>
-
导入组件和样式
App.vue
<template> <div class="todoapp"> <TodoHeader></TodoHeader> <TodoMain></TodoMain> <TodoFooter></TodoFooter> </div> </template> <script> import './styles/base.css' import './styles/index.css' import TodoHeader from './components/todoHeader.vue' import TodoMain from './components/todoMain.vue' import TodoFooter from './components/todoFooter.vue' export default { components: { TodoHeader, TodoMain, TodoFooter }, } </script>