vue.js 精学组件记录
Posted liuzhixiang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue.js 精学组件记录相关的知识,希望对你有一定的参考价值。
组件需要注册后才可以使用。
Vue.component(‘my-component‘,{
template:‘<div>这是组件内容</div>‘
});
局部注册组件
var Child = { template:‘<div>局部注册组件内容</div>‘ } var app = new Vue({ el:"#app", components: { ‘my-component‘:Child } })
Vue组件模板在某些情况下会受到限制,比如table,ul,ol.select等,这时可以类似这样:
<table> <tbody is="my-component"></tbody> </table>
还可使用数据绑定,但data必须是函数:
Vue.component(‘my-component‘,{ template:‘<div>{{message}}</div>‘, data:function(){ return { message:‘局部注册内容,data方法return‘ } } })
使用props传递数据
<my-component message="来自父组件的信息"></my-component>
Vue.component(‘my-component‘,{ props:[‘message‘], template:‘<div>{{message}}</div>‘ })
父组件动态数据:
<input type="text" v-model="message"/> <my-component :message="message"></my-component>
Vue.component(‘my-component‘,{ props:[‘message‘], template:‘<div>{{message}}</div>‘ }) var app = new Vue({ el:"#app", data:{ message:‘‘ } })
当输入框输入时,子组件接收到的props "message" 也会实时响应,并更新组件模板。
注意:
如果直接传递数字,布尔值,数组,对象,而不是用v-bind,传递的是字符串。
<my-component message="[1,2,3]"></my-component> <my-component :message="[1,2,3]"></my-component>
Vue.component(‘my-component‘,{ props:[‘message‘], template:‘<div>{{message.length}}</div>‘ })
此时上一个会输出字符串的长度7,下面才是输出数组的长度3。
单向数据流
父组件传递初始值进来,子组件作为初始值保存,在自己的作用域下可随意修改。
<div id="app"> <my-component :init-count="1"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ props:[‘initCount‘], template:‘<div>{{count}}</div>‘, data:function(){ return { count:this.initCount+1 } } }) var app = new Vue({ el:"#app", data:{ message:‘‘ } }) </script>
<div id="app"> <my-component :width="100"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ props:[‘width‘], template:‘<div :style="style">组件内容</div>‘, computed:{ style:function(){ return { width:this.width+"px", background:‘red‘ } } } }) var app = new Vue({ el:"#app", data:{ message:‘‘ } }) </script>
子组件传递数值到父组件:
<div id="app"> <p>总数:{{total}}</p> <my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:‘<div><button @click="handleIncrease">+1</button><button @click="handleReduce">-1</button></div>‘, data:function(){ return { counter: 0 } }, methods:{ handleIncrease:function(){ this.counter++; this.$emit(‘increase‘,this.counter); }, handleReduce:function(){ this.counter--; this.$emit(‘reduce‘,this.counter); } } }); var app = new Vue({ el:"#app", data:{ total:0 }, methods:{ handleGetTotal:function(total){ this.total=total console.log(total) } } });
在子组件定义两个事件,改变自身数据的同时使用$emit来触发父组件的自定义事件名称,从而触发对应的方法,再把改变的数据通过方法传到父组件的方法,从而改变父组件的数据。
使用v-model
<div id="app"> <p>总数:{{total}}</p> <my-component v-model="total"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:‘<div><button @click="handleIncrease">+1</button></div>‘, data:function(){ return { counter: 0 } }, methods:{ handleIncrease:function(){ this.counter++; this.$emit(‘input‘,this.counter); } } }); var app = new Vue({ el:"#app", data:{ total:0 } }); </script>
这次区别是$emit通知的是特殊的input,但是并没有在组件上使用@input,这是因为v-model,这也可以称作是一个语法糖。
因为上面的示例可以间接实现:
<my-component @input="total"></my-component>
v-model还可以用来创建自定义的表单输入组件,进行数据双向绑定:
<div id="app"> <p>总数:{{total}}</p> <my-component v-model="total"></my-component> <button @click="handleReduce">-1</button> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ props:[‘value‘], template:‘<input :value="value" @input="updateValue" />‘, methods:{ updateValue:function(event){ this.$emit(‘input‘,event.target.value); } } }); var app = new Vue({ el:"#app", data:{ total:0 }, methods:{ handleReduce:function(){ this.total--; } } }); </script>
子组件input改变value值后出发updateValue,通过$emit触发特殊的input事件,传值输入的value值,改掉父组件的total,父组件的handleReduce方法改变total后,由于是双向绑定,所以子组件的value值也随着改变。
这需要满足两个条件:
1.接收一个props的value值,
2.更新value触发特殊的input事件
非父子组件通信
在vue1.x中是采用$dispatch()和$broadcast()这两个方法。$dispatch()由于向上级派发事件,只要是它的父级,都可在vue实例的events选项内接收。
在vue2.x中废弃了上述两种方法。在vue2.x中推荐使用一个空的vue实例作为中央事件总线(bus),也就是一个中介。
如下:
<div id="app"> <p>信息:{{message}}</p> <my-component></my-component> </div> <script type="text/javascript"> var bus = new Vue(); Vue.component(‘my-component‘,{ template:‘<button @click="handleEvent"></button>‘, methods:{ handleEvent:function(event){ bus.$emit(‘on-message‘,‘来自组件component的内容‘); } } }); var app = new Vue({ el:"#app", data:{ message:‘‘ }, mounted:function(){ var _this = this; bus.$on(‘on-message‘,function(msg){ _this.message=msg; }) } }); </script>
定义一个空的vue实例当做中间人,在钩子函数mounted中监听了来自bus的事件on-message,在组件中会点击按钮通过bus把事件on-message发出去,此时app会接收到来自bus的事件。
除了中间介这种方式外,还有两种方法可实现组件间的通信:父链和子组件索引。
父链
子组件中使用this.$parent可直接访问父实例或组件,父组件也可以通过this.$children访问它所有子组件。
<div id="app"> <p>信息:{{message}}</p> <my-component></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:‘<button @click="handleEvent">通过$parent改变信息内容</button>‘, methods:{ handleEvent:function(event){ this.$parent.message=‘子组件通过$parent改变了信息内容‘; } } }); var app = new Vue({ el:"#app", data:{ message:‘‘ } }); </script>
子组件内通过this.$parent.message直接修改信息内容,正常使用不推荐使用。因为这样使得父子组件紧耦合,只看父组件,很难理解父组件状态,因为它可能被任意组件修改。最好还是通过props和$emit来通信。
子组件索引
<div id="app"> <button @click="handleRef">通过red获取子组件实例</button> <my-component ref="Mycom"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:‘<div>子组件</div>‘, data:function() { return { message:‘子组件内容‘ } } }); var app = new Vue({ el:"#app", methods:{ handleRef:function(){ var msg = this.$refs.Mycom.message; console.log(msg) } } }); </script>
父组件内的内容通过this.$refs.Mycom找到子组件,并输出了子组件的内容。
使用slot分发内容
当需要让组件组合使用,混合父组件发的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发。
slot用法
单个slot: 在子组件标签内的素有内容替代子组件的<slot>标签及它的内容。如下:
<div id="app"> <child-component> <p>分发的内容</p> <p>更多分发的内容</p> </child-component> </div> <script type="text/javascript"> Vue.component(‘child-component‘,{ template:‘<div><slot><p>如果没有父组件插入内容,我将默认出现</p></slot></div>‘, }); var app = new Vue({ el:"#app", }); </script>
当父组件没有插入分发的内容,即上述代码没有
<p>分发的内容</p>
<p>跟多分发的内容</p>
的时候,子组件将默认显示子组件里slot的内容,如果父组件里插入了分发的内容,则将替换掉子组件里slot标签里的全部内容。
具名slot
给slot元素指定一个name后可以分发多个内容,具名slot可以与单个slot共存。
如下:
<div id="app"> <child-component> <h2 slot="header">标题</h2> <p>正文内容</p> <p>更多正文内容</p> <div slot="footer">底部信息</div> </child-component> </div> <script type="text/javascript"> Vue.component(‘child-component‘,{ template:`<div class="container"> <div class="header"> <slot name="header"></slot> </div> <div class="main"> <slot></slot> </div> <div class="footer"> <slot name="footer"></slot> </div> </div>`, }); var app = new Vue({ el:"#app", }); </script>
在父组件上面可以具名插入slot的name,然后子组件会更具name插入具体的内容。
作用域插槽
是一种特殊的slot使用一个可以复用的模板替换已渲染的元素。
<div id="app"> <child-component> <template scope="props"> <p>来自父组件的内容</p> <p>{{props.msg}}</p> </template> </child-component> </div> <script type="text/javascript"> Vue.component(‘child-component‘,{ template:`<div class="container"> <slot msg="来自子组件的内容"></slot> </div>`, }); var app = new Vue({ el:"#app", }); </script>
子组件模板,在slot元素上有一个类似pros传递数据给组件的写法 msg="XXX",将数据传到了插槽。父组件使用了template且拥有一个scope="props"的特性,这里的props知识一个临时变量,通过临时变量props来访问来自子组件的数据msg。渲染的最终结果为:
<div id="app"> <div class="container"> <p>来自父组件的内容</p> <p>来自子组件的内容</p> </div> </div>
作用域插槽具代表性的用例是列表组件,允许组件自定义应该如何渲染列表的每一页。
<div id="app"> <my-list :book="books"> <!--作用域插槽也可以是具名的slot--> <template slot="book" scope="props"> <li>{{props.bookName}}</li> </template> </my-list> </div> <script type="text/javascript"> Vue.component(‘my-list‘,{ props:{ books:{ type:Array, default:function(){ return []; } } }, template:‘<ul><slot name="book" v-for="book in books" :bookName="book.name"></slot></ul>‘, }); var app = new Vue({ el:"#app", data:{ books:[ {name:‘js‘}, {name:‘css‘}, {name:‘html‘} ] } }); </script>
子组件my-list接收一个来自父级的prop数组books,并且将它在name为book的slot上使用v-for指令循环,同时暴露一个变量bookName。
访问slot
vue2.x提供了用来访问被slot分发的内容的方法$slots。
<div id="app"> <my-component> <h2 slot="header">标题</h2> <p>正文内容</p> <div slot="footer">底部信息</div> </my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:`<div class="container"> <div class="header"> <slot name="header"></slot> </div> <div class="main"> <slot></slot> </div> <div class="footer"> <slot name="footer"></slot> </div> </div>`, mounted:function(){ var header = this.$slots.header; var main = this.$slots.default; var footer = this.$slots.footer; console.log(footer[0].elm.innerHTML) } }); var app = new Vue({ el:"#app", }); </script>
在子组件钩子函数中能用$slots得到对应的slot。
组件的高级用法
递归组件
组件可以在它的模板内调用自己,只要给组件设置name属性就可以。
<div id="app"> <my-component :count="1"></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ name:‘my-component‘, props:{ count:{ type:Number, default:function(){ return 1; } } }, template:‘<div class="child"><my-component :count="count+1" v-if="count<3"></my-component></div>‘, }); var app = new Vue({ el:"#app", }); </script>
设置name后就可以递归使用了,不过需要一个限制条件,不然会报错。
内联模板
vue提供了一个内敛模板的功能,在使用组件时,给组件标签使用inline-template特性,组件就会把它的内容当做模板,而不是把它当内容分发。
<div id="app"> <my-component inline-template> <div> <h2>父组件定义子组件模板</h2> <p>{{message}}</p> <p>{{msg}}</p> </div> </my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ data:function(){ return { msg:‘子组件内容‘ } } }); var app = new Vue({ el:"#app", data:{ message:‘父组件内容‘ } }); </script>
渲染成:
<div> <h2>父组件定义子组件模板</h2> <p>父组件内容</p> <p>子组件内容</p> </div>
使用内联模板会导致作用域混淆,应避免使用。
动态组件
vue.js提供了一个特殊的元素<component>用来动态挂载不同的组件,使用is特性来选择挂载的组件。
<div id="app"> <component :is="currentView"></component> <button @click="handleChangeView(‘A‘)">切换到A</button> <button @click="handleChangeView(‘B‘)">切换到B</button> <button @click="handleChangeView(‘C‘)">切换到C</button> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", components:{ comA:{ template:‘<div>A模板</div>‘ }, comB:{ template:‘<div>B模板</div>‘ }, comC:{ template:‘<div>C模板</div>‘ }, }, data:{ currentView:‘comA‘, }, methods:{ handleChangeView:function (component){ this.currentView=‘com‘+component } } }); </script>
动态地改变currentView就可以切换视图。
异步组件
当工程足够大,使用组件足够多时,是时候考虑下性能了,因为一开始把所有组件都加载时时一笔开销。vue.js允许将组件定义为一个工厂函数,动态地解析组件。
<div id="app"> <my-component></my-component> </div> <script type="text/javascript"> Vue.component(‘my-component‘,function(resolve,reject){ setTimeout(()=>{ resolve({ template:‘<div>我是异步渲染的</div>‘ }) },2000) }); var app = new Vue({ el:"#app", }); </script>
$nextTick
当v-if,动态切换显示时,直接去取div的内容时获取不到的,因为此时div还没有被创建出来。这里涉及到一个vue的重要概念:异步更新队列。
vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的数据变化。
$nextTick就是用来知道DOM什么时候更新好的。
X-template
没有使用webpack,gulp等工具,组件内容复杂,如果都在javascript中拼接字符串,效率很低,vue提供了另一种定义模板的方式,在script标签使用text/template类型,指定一个ID,将这个id赋给template。
<div id="app"> <my-component></my-component> <script type="text/x-template" id="my-component"> <div>这是组件内容</div> </script> </div> <script type="text/javascript"> Vue.component(‘my-component‘,{ template:"#my-component" }); var app = new Vue({ el:"#app", }); </script>
vue的初衷不是滥用它,因为它将模板和组件的其他定义隔离了。
以上是关于vue.js 精学组件记录的主要内容,如果未能解决你的问题,请参考以下文章