什么是组件
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="components-demo"><button-counter></button-counter><button-counter></button-counter></div><script>// 定义一个名为 button-counter 的新组件Vue.component('button-counter', {data: function () {return {count: 0}},template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'})new Vue({ el: '#components-demo' })</script></body></html>
这些类似于
使用组件的好处:
提高开发效率
方便重复使用
简化调试步骤
提升整个项目的课维护性
便于协同开发
组件系统让我们可以用独立可复用的小组件来构建大型应用,
几乎任意类型的应用的界面都可以抽象为一个组件树,如图5-1所示
全局注册
全局注册有三种方式。
要注册一个全局组件,我们可以使用 Vue.component(tagName,options),代码如下:
Vue.component('my-component', {// 选项})
<div id="app"><my-component></my-component></div><script>Vue.component('my-component', {template: '<h1>注册</h1>'});var vm=new Vue({el:'#app'})</script>
使用 Vue.extend 配合 Vue.component 方法
<div id="app"><my-list></my-list></div><script>var list=Vue.extend({template:'<h1>this is a list</h1>',});Vue.component("my-list",list);//根实例new Vue({el:"#app",})</script>
将模板字符串,定义到 script 标签中
<div id="app"><account></account></div><template id="tmpl"><div><a href="#">登录</a> | <a href="#">注册</a></div></template><script>Vue.component('account', {template: '#tmpl'});new Vue({el:"#app",})</script>
局部注册
如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的 components 属性实现局部注册
<div id="app"><account></account></div><script>// 创建 Vue 实例,得到 ViewModelvar vm = new Vue({el: '#app',data: {},methods: {},components: { // 定义子组件account: { // account 组件template: '<div><h1>这是Account组</h1><login></login></div>', // 在这里使用定义的子组件components: { // 定义子组件的子组件login: { // login 组件template: "<h3>这是登录组件</h3>"}}}}});</script>
组件的基本使用
可以使用“flag”标识符结合“v-if”和“v-else”切换组件
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="app"><input type="button" value="toggle" @click="flag=!flag"><account v-if="flag"></account><login v-else="flag"></login></div><script>// 创建 Vue 实例,得到 ViewModelvar vm = new Vue({el: '#app',data: {flag: true},methods: {},components: { // 定义子组件account: { // account 组件template: '<div><h1>这是Account组件</h1></div>', // 在这里使用定义的子组件},login: { // login 组件template: "<h3>这是登录组件</h3>"}}});</script></body></html>
DOM模板解析说明
在自定义组件中使用这些受限制的元素时会导致一些问题
<table><my-row>...</my-row></table>
自定义组件被认为是无效的内容,因此在渲染的时候会导致错误。这时要使用特殊的 is 属性来挂载组件
<table><tr is="my-row"></tr></table>
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="app"><table border="1" cellpadding="5" cellspacing="0"><my-row></my-row><tr is="my-row"></tr></table></div><script type="text/javascript">new Vue({el:'#app',components:{myRow:{template:'<tr><td>123456</td></tr>'}}});</script></body></html>
组件选项
Vue 的组件最核心的选项有以下几个:
模板(template)
初始数据(data)
接受的外部参数(props)
方法(methods)
生命周期钩子函数(lifecycle hooks)
组件接受的选项大部分与 Vue 实例一样,相同的部分就不再赘述了。我们重点说一下 二者不同的选项 data 和 props,data 在 5.2.1 中已经讲解过了,所以本小节主要讲解 props, 它用于接收父组件传递的参数。
组件 props
组件中更重要的是组件间进行通信,选项props是组件中非常重要的一个选项,起到父子组件间桥梁的作用。
静态props
子组件使用父组件的数据
<div id="app"><my-componet message="来至父组件的数据!!"></my-componet></div><script type="text/javascript">Vue.component('my-componet', {// 声明 propsprops: ['message'],// 就像 data 一样,prop 可以用在模板内// 同样也可以在 vm 实例中像 “this.message” 这样使用template: '<span>{{ message }}</span>'})new Vue({el:'#app'});</script>
动态 props
动态接收父组件数据到子组件
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="app"><input type="text" v-model="parentMessage"><my-componet :message="parentMessage"></my-componet></div><script type="text/javascript">Vue.component('my-componet', {props: ['message'],template: '<span>{{ message }}</span>'})new Vue({el:'#app',data:{parentMessage:''}});</script></body></html>
这里使用 v-model 绑定了父组件数据 parentMessage,当在输入框中输入数据时,子组件接收到的 props“’message’”也会实时响应,并更新组件模板。
对于初学者常犯的一个错误,如果你在父组件中直接传递数字、布尔值、数组、对象时, 它所传递默认值字符串。如果想传递一个实际的数值,需要使用 v-bind ,从而让它的值被当作 JavaScript 表达式计算
<div id="app"><my-componet message="1+1"></my-componet><br><my-componet :message="1+1"></my-componet></div><script type="text/javascript">Vue.component('my-componet', {props: ['message'],template: '<span>{{ message }}</span>'})new Vue({el:'#app'});</script>
props 验证
当组件给其他人使用时,推荐进行数据验证。
验证的type类型可以是:String、Number、Boolean、Function、Object、Array等
如果传入子组件的 message 不是数字,则抛出警告
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="example"><parent></parent></div><script>var childNode = {template: '<div>{{message}}</div>',props:{'message':Number}}var parentNode = {template:'<div class="parent"><child :message="msg"></child></div>',components: {'child': childNode},data(){return{msg: '123'}}};new Vue({ // 创建根实例el: '#example',components: {'parent': parentNode}})</script></body></html>
单项数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。之所以这样设计,是尽可能将父子组件解耦,避免子组件无意中修改了父组件的状态
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="example"><parent></parent></div><script>var childNode = {template:'<div class="child"><div><span>子组件数据</span>' +'<input v-model="childMsg"> </div> <p>{{childMsg}}</p></div>' ,props:['childMsg']}var parentNode = {template:'<div class="parent"><div><span>父组件数据</span>' +'<input v-model="msg"></div><p>{{msg}}</p> <child :child-msg="msg"></child></div>',components: {'child': childNode},data(){return {'msg':'match'}}};new Vue({// 创建根实例el: '#example',components: {'parent': parentNode}})</script></body></html>
运行结果:父组件数据变化时,子组件数据会相应变化;而子组件数据变化时,父组件数据不变,并在控制台显示警告
组件通信

组件关系有下面三种:父—>子、子—>父、非父子。
自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="app"><my-component v-on:myclick="onClick"></my-component></div><script>Vue.component('my-component', {template:'<div>' +'<button type="button" @click="childClick">点击我触发自定义事件</button></div>' ,methods: {childClick () {this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')}}})new Vue({el: '#app',methods: {onClick () {console.log(arguments)}}})</script></body></html>
on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex
var Event=new Vue();Event.$emit(事件名,数据);Event.$on(事件名,data => {});
基础用法
标签
//部分代码省略<div class="" id="app"><my-component><p>hi,slots</p></my-component></div><script>Vue.component('my-component', {template:'<div><slot></slot><div>'});new Vue({el: "#app"});</script>
尽管内容分发这个概念看起来极为复杂,而实际上可以简单了解为把HTML标签传入组件的一种方法。所以归根结底,内容分发是一种为组件传递参数的方法。
编译作用域
组件作用域简单地说是:父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="app"><child-component v-show="someChildProperty"></child-component></div><script>Vue.component('child-component', {template: '<div>这是子组件内容</div>',data: function () {return {someChildProperty: true}}})new Vue({el:'#app'})</script></body></html>
这里someChildProperty绑定的是父组件的数据,所以是无效的,获取不到数据。如果想在子组件上绑定,可以是如下代码
<div id="app"><child-component ></child-component></div><script>Vue.component('child-component', {// 有效,因为是在正确的作用域内template: '<div v-show="someChildProperty">这是子组件内容</div>',data: function () {return {someChildProperty: true}}})new Vue({el:'#app'})</script>
默认 slot
如果要父组件在子组件中插入内容 ,必须要在子组件中声明slot 标签 ,如果子组件模板不包含插口,父组件的内容将会被丢弃
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="app"><!-- 1.2 那组件innerHTML位置以后不管有任何代码,都会被放进插槽那个坑里面去 --><index><span>首页</span><span>首页</span><span>首页</span><h1>手机</h1></index></div><script>// 插槽的作用就是组件外部取代码片段放到组件内部来// 定义默认插槽通过slot组件定义,定义好了之后,就相当于一个坑,你可以把它理解为电脑上usb插口Vue.component('index', {template:' <div>index</div>'})var vm = new Vue({el: '#app',})</script></body></html>
页面显示结果为:index。所有子组件中的内容都不会被显示,被丢弃。
要想父组件在 子组件中插入内容,必须要在子组件中声明 slot 标签,
<script>vue. component ( 'index', {template : '<div><slot></slot>index </div>'})var vm=new vue ( {el : '#app ' ,})</script>
具名 slot
slot 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 标签可以有不同的名字。
使用方法。
父组件要在分发的标签中添加属性”slot=name 名”。
子组件在对应分发位置上的 slot 标签添加属性”name=name 名”
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="app"><child><span slot="one">123456</span><span slot="two">abcdef</span></child></div><script>new Vue({el:'#app',components:{child:{template:"<div><slot name='two'></slot>我是子组件<slot name='one'></slot></div>"}}});</script></body></html>
作用域插槽
作用域插槽更具代表性的应用是列表组件,允许组件自定义应该如何渲染列表每一项
<div id="app"><child></child></div><script>Vue.component('child', {data(){return {list:[1,2,3,4]}},template: '<div> <ul>' +'<li v-for="item of list">{{item}}</li></ul></div>',})var vm = new Vue({el: '#app'})</script>
如果需要child组件在很多地方会被调用,我希望在不同的地方调用child的组件时,这个列表到底怎么循环,列表的样式不是child组件控制的,而是外部child模版占位符告诉我们组件的每一项该如何渲染,也就是说这里不用li标签,而是要用slot标签
<div id="app"><child><template slot-scope="props"> <!--固定写法,属性值可以自定义--><li>{{props.item}}</li> <!--用插值表达式就可以直接使用--></template></child></div><script>Vue.component('child', {data(){return {list:[1,2,3,4]}},template: '<div> <ul>' +'<slot v-for="item of list" :item=item></slot> </ul></div>',})var vm = new Vue({el: '#app'})</script>
动态组件
让多个组件使用同一个挂载点,并动态切换,这就是动态组件。通过使用保留的
<!---省略部分代码--><div id="app"><button @click="change">切换页面</button><component :is="currentView"></component></div><script>new Vue({el: '#app',data:{index:0,arr:[{template:'<div>我是主页</div>'},{template:'<div>我是提交页</div>'},{template:'<div>我是存档页</div>'}],},computed:{currentView(){return this.arr[this.index];}},methods:{change(){this.index = (++this.index)%3;}}})</script></body></html>
component 标签中 is 属性决定了当前采用的子组件,:is 是 v-bind 的简写,绑定了父组 件中 data 的 currentView 属性。点击按钮时,会更改数组 arr 的索引值,同时也修改了子组 件的内容。
keep-alive
<!--省略部分代码--><div id="app"><button @click="change">切换页面</button><keep-alive><component :is="currentView"></component></keep-alive></div><script>new Vue({el: '#app',data:{index:0,arr:[{template:'<div>我是主页</div>'},{template:'<div>我是提交页</div>'},{template:'<div>我是存档页</div>'}],},computed:{currentView(){return this.arr[this.index];}},methods:{change(){/*es6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。*/let len = this.arr.length;this.index = (++this.index)% len;}}})</script></body></html>
activated 钩子函数
<!DOCTYPE html><html><head><title></title><meta charset="utf-8"/><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><div id="app"><button @click='toShow'>点击显示子组件</button><!----或者<component v-bind:is="which_to_show" keep-alive></component>也行-----><keep-alive><component v-bind:is="which_to_show" ></component></keep-alive></div><script>// 创建根实例var vm = new Vue({el: '#app',data: {which_to_show: "first"},methods: {toShow: function () { //切换组件显示var arr = ["first", "second", "third", ""];var index = arr.indexOf(this.which_to_show);if (index < 2) {this.which_to_show = arr[index + 1];} else {this.which_to_show = arr[0];}console.log(this.$children);}},components: {first: { //第一个子组件template: "<div>这里是子组件1</div>"},second: { //第二个子组件template: "<div>这里是子组件2,这里是延迟后的内容:{{hello}}</div>",data: function () {return {hello: ""}},activated: function (done) { //执行这个参数时,才会切换组件console.log('beixi')var self = this;var startTime = new Date().getTime(); // get the current time//两秒后执行while (new Date().getTime() < startTime + 2000){self.hello='我是延迟后的内容';}}},third: { //第三个子组件template: "<div>这里是子组件3</div>"}}});</script></body></html>
异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从 服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个 工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染
<div id="app"><async-example></async-example></div><script>Vue.component ( 'async-example ', function (resolve, reject){setTimeout (function (){//向resolve 回调传递组件定义resolve ( {template: '<div>这是异步渲染的内容!</div>'})},1000)})new vue ( {el : '#app'})</script>
如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到 组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。这里的 setTimeout 是 为了演示异步,如何获取组件取决于你自己。比如把组件配置成一个对象配置,通过 Ajax 来请求,然后调用 reslove 传入配置选项。
5.6.5 ref 和$refs(了解即可)
在 Vue 中一般很少会用到直接操作 DOM,但不可避免有时候需要用到,这时我们可以 通过 ref 和$refs 来实现:
ref: ref 被用来给元素或子组件注册引用信息, 引用信息将会注册在父组件 的 $refs 对象上,如果是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素,如果是 在子组件上,引用就指向组件的实例。
refs 是一个对象,持有已注册过 ref 的所有的子组件。
普通获取 DOM 的方式
普通获取 DOM 的方式
<div id="app"><input type="button" value="获取h3的值"@click="getElement"><h3 id="myh3">我是h3</ h3></div><script>var vm=new vue ( {el : "#app" ,data : { } ,methods : {getElement ( ) {//通过getElementById方式获取DOM对象console.log (document. getElementById ( "myh3" ) .innerHTML) ;}}})</script>
ref 使用
<div id="app"><input type="button" value="获取h3的值"@click="getElement"><h3 id="myh3" ref="myh3">我是h3</h3></div>
3.ref在组件中使用
在子组件中使用 ref属性,会将子组件添加到父组件的$refs对象中,
<div id="app"><input type="button" value="获取h3的值" @click="getElement"><h3 id="myh3" ref="myh3">我是h3</h3><hr><login ref="mylogin"></login></div>
