上一篇:vue生命周期深刻
下一篇:Vue组件通讯深刻Vuexjavascript
建议:博客中的例子都放在vue_blog_project工程中,推荐结合工程实例与博客一同窗习css
vue中,组件是带有一个名字、可复用的 Vue 实例。因为 Vue 是面向视图的MVVM框架, 组件能够看作是对数据和方法的简单封装、具备独立的逻辑和功能的界面,多个组件按照必定规则的组合最终成为一个完整的应用
Vue.component()
用来建立全局组件,一旦注册,便可在该实例Vue下的任何子组件中使用,经常使用于一些使用较为频繁的基础组件,如Alert组件、Button组件、布局组件等html
使用方式:vue
Vue.component('my-component', { // vue实例方法和生命周期(el除外) })
若是你使用过 element-ui
,下面的写法你可能比较熟悉:java
import Vue from 'vue'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue'; Vue.use(ElementUI); new Vue({ el: '#app', render: h => h(App) });
其中Vue.use(ElementUI);
的方式即是间接调用了全局组件注册的方式,在element-ui
内部:
(插件中,使用Vue.use()
的方式,至关于调用了其中的install方法)webpack
const install = function(Vue, opts = {}) { // ... components.map(component => { Vue.component(component.name, component); }); // ... };
能够看出,在其内部也是依次全局注册了element
中的插件git
Vue官网上如是说:github
全局注册每每是不够理想的。好比,若是你使用一个像 webpack 这样的构建系统,全局注册全部的组件意味着即使你已经再也不使用一个组件了,它仍然会被包含在你最终的构建结果中。这形成了用户下载的 JavaScript 的无谓的增长。
正是由于上面的缘由,除了一些经常使用的基础组件外,尽量的使用局部注册的方式web
// 普通引入方式 var ComponentA = { /* ... */ } // ES6引入方式 import ComponentA from './ComponentA.vue' export default { // ... components: { 'component-a': ComponentA, } }
值得注意的是:局部注册方式仅能在当前组件中使用,在其子组件中使用须要再次注册element-ui
上面提到,多个组件按照必定规则的组合最终成为一个完整的应用
,所以,咱们能够将组件看做是Vue页面中的最小单元,那么应该如何组织组件,整合成一个页面呢?
有这样一个需求:要求按照下图组织页面结构
咱们能够这样组织:
<template> <div class="m-body"> 我是主体内容 </div> </template> <script> export default {} </script> <style scoped> .m-body { min-height: 500px; color: #fff; padding: 20px; background-color: #39f; } </style>
按照这种方式,依次写出header、aside、content、footer四个组件,并用一个组件做为这四个组件的父组件来组织页面结构,最后的结构以下:
父组件以下:
<template> <div class="comp"> <m-header /> <div class="main"> <m-side /> <m-body /> </div> <m-footer /> </div> </template> <script> import MHeader from './MHeader' import MFooter from './MFooter' import MBody from './MBody' import MSide from './MSide' export default { components: { MHeader, MFooter, MBody, MSide } } </script> <style lang="scss" scoped> .main { margin: 10px 0; display: flex; .m-side { width: 200px; margin-right: 10px; } .m-body { flex: 1; } } </style>
打开Vue调试界面,将看到以下的结构
注意:父组件负责控制容器结构样式(各个直接子组件的位置、大小等),子组件负责其内部的样式,不要在子组件中写本身的容器样式
组件的组合仅仅只是将页面结构搭建了起来,要完成页面的交互功能,组件之间一定会有数据传递
按照页面结构,大致上能够将组件间的数据传递分红两种:
Vue官网中对props、$emit、slot有很是详细的描述,在此再也不唠述
现有新的 需求:在上面例子的基础上,须要知足:header中有一个数值,side中新增重置和增长按钮,body中新增数组输入框,当对按钮和表单做操做时,对应的数值做相应改变
基本思路:将数值放在几个组件公共上层组件中,header中prop接受该值,side和body中点击按钮向他们的公共上层组件分发$emit事件,改变该数值,核心思路:多个组件操做的值均为上层组件的变量
代码以下:
(1)父级组件:主要用于数据传递与接收子组件分发的事件来改变对应的变量
<div class="comp"> <m-header :num="num" /> <div class="main"> <m-side @add="handleAdd" @reset="handleReset" /> <m-body :num="num" @change="handleChange" /> </div> <m-footer /> </div>
export default { data () { return { num: 0 } }, methods: { handleAdd () { this.num += 1 }, handleChange (val) { this.num = val }, handleReset () { this.num = 0 } }, // ... }
(2)Header组件:接受并展现数值
template中仅添加{{ num }}
props: { num: { type: Number, default: 0 } }
(3)Side组件:向上分发增长和重置事件
<!-- 新增 --> <el-button @click="add">ADD</el-button> <el-button @click="reset">RESET</el-button>
methods: { add () { this.$emit('add') }, reset () { this.$emit('reset') } }
(4)Body组件:监控传值,向上分发事件
<!-- 新增 --> <el-input-number v-model="currentVal" @change="handleChange"></el-input-number>
props: { num: { type: Number, default: 0 } }, data () { return { currentVal: 0 } }, // 外层数据改变时,currentVal值须要同步修改 watch: { num: { handler (val) { this.currentVal = val }, immediate: true } }, methods: { handleChange (val) { this.$emit('change', val) } }
这种简单的数据交互使用prop和$emit足以应付,可是
(1)对于深层组件嵌套中的数据传递,使用这种通讯方式则须要一层一层向下prop,改变时须要一层一层向上$emit
(2)对于兄弟组件之间的数据传递,先要向上分发,再向下prop,过于繁琐且不易监控调试
这里有一个 新的需求:在最初组件组合的基础上,side组件中有一个数据展现,要求经过body中深层嵌套的组件操做以改变side中的数据
修改:在body组件中添加<slot></slot>
,并新增一个组件挂载在该插槽上,用以模拟深层嵌套(固然了,实际的工做中的嵌套可能涉及到四层甚至更多)
上面方法的核心是全部子组件统一管理和操做父组件的数据,子组件负责展现和分发事件,实际操做值的始终在父组件,Vue提供了一个能访问到根组件的方法,官网中如是描述:处理边界状况中访问根实例部分
(1)在入口文件main.js
中添加:
new Vue({ data: { rootNum: 0 }, // ... })
(2)在父组件中添加:
<!-- 局部注册不做详述 --> <m-body> <m-body-item></m-body-item> </m-body>
(3)新添加的组件MBodyItem
<template> <div class="m-body-item"> <el-button @click="add">ADD</el-button> <el-button @click="reset">RESET</el-button> </div> </template> <script> // 可直接操做$root中声明的变量 export default { methods: { add () { this.$root.rootNum += 1 }, reset () { this.$root.rootNum = 0 } } } </script>
(4)side组件:
<div class="m-side"> 我是侧边栏{{ $root.rootNum }} </div>
对于 demo 或很是小型的有少许组件的应用来讲直接使用$root的方式很方便。不过这个模式扩展到中大型应用来讲就否则了,数据量过大不易维护,也不易追踪数据的变化
总线Bus的思路:将事件的注册和触发单独放在一个Vue实例中,点击按钮时触发指定的事件以驱动接下来的操做。Bus总线仅仅是用来驱动事件的,具体的数据操做仍是在原有的组件中
在$root的结构基础上,做以下更改:
(1)原入口文件main.js
还原,去掉data属性
(2)新定义一个总线文件bus.js
import Vue from 'vue' export default new Vue()
(3)side组件中注册总线事件并显示数据
import Bus from './bus' export default { data () { return { sideNum: 0 } }, created () { Bus.$on('change', (step) => { this.sideNum += step }) Bus.$on('reset', () => { this.sideNum = 0 }) } }
(4)bodyItem组件中分发总线事件
import Bus from './bus' export default { methods: { add () { Bus.$emit('change', 1) }, reset () { Bus.$emit('reset') } } }
总线的方式,将原有的数据传递转换成了事件驱动的形式,这一点规避了组件层级的嵌套问题,可是开发人员没法追踪调试数据
因为内容较多,将在下一篇博客中详细介绍,敬请期待..