共计 7676 个字符,预计需要花费 20 分钟才能阅读完成。
Vue 组件基本示例
组件是可复用的实例 ,且 带有一个名字 ,下面的例子中,button-counter 就是一个实例. 我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用
因为组件是可复用的 Vue 实例,所以它们 与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
<div id="components-demo"> | |
<button-counter></button-counter> | |
</div> |
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'}) |
注意:要先注册组件(Vue.component), 再创建 vue 的实例,否则会出现以下的报错:
vue.js:634 [Vue warn]: Unknown custom element: <button-counter> – did you register the component correctly? For recursive components, make sure to provide the “name” option.(found in <Root>)
一个组件的 data 选项 必须是一个 函数 ,因此每个实例可以维护一份被返回对象的独立的拷贝。 用一次组件,就会有一个它的新实例被创建。
组件注册
Vue.component('my-component-name', {// ... 选项 ...})
组件名 (W3C:字母全小写且必须包含一个连字符)
组件名定义方式有两种:使用 kebab-case 或 PascalCase
也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。
组件注册分为 全局 注册和 局部 注册
全局注册
全局 注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例 ,也包括 其组件树中的所有子组件的模板 中。
<div id="global"> | |
<component-a></component-a> | |
<component-b></component-b> | |
<component-c></component-c> | |
</div> | |
Vue.component('component-a',{data:function () { | |
return{clicked:0} | |
}, | |
template:'<button v-on:click="clicked++">You clicked me {{clicked}}times</button>' | |
}) | |
Vue.component('component-b',{data:function () { | |
return{clicked:0} | |
}, | |
template:'<button v-on:click="clicked++">You clicked me {{clicked}}times</button>' | |
}) | |
Vue.component('component-c',{data:function () { | |
return{clicked:0} | |
}, | |
template:'<div><span>You have clicked</span><input type="button"v-model="clicked"v-on:click="clicked++"><span>times.</span></div>' | |
}) | |
new Vue({el:'#global'}) |
局部注册
局部注册的组件在其 子组件 中不可用
<div id="part"> | |
<component-a></component-a> | |
<component-b></component-b> | |
<component-c></component-c> | |
</div> | |
var ComponentA = {data:function () { | |
return {clicked:0} | |
}, | |
template:'<button v-on:click="clicked++">You clicked me {{clicked}}times</button>' | |
} | |
var ComponentB = {data:function () { | |
return {clicked:0} | |
}, | |
template:'<button v-on:click="clicked++">You clicked me {{clicked}}times</button>' | |
} | |
var ComponentC = {data:function () { | |
return {clicked:0} | |
}, | |
template:'<button v-on:click="clicked++">You clicked me {{clicked}}times</button>' | |
} | |
new Vue({ | |
el:'#part', | |
components:{ | |
'component-a':ComponentA, | |
'component-b':ComponentB, | |
'component-c':ComponentC, | |
} | |
}) |
对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
通过 Prop 向子组件传递数据
Prop 是你可以在组件上注册的一些自定义特性。Prop 是你可以在组件上注册的一些自定义特性。一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。一个 prop 被注册之后,你就可以像这样把数据作为一个自定义特性传递进来
<div id="prop-demo"> | |
<blog-post title="prop just like data"></blog-post> | |
</div> | |
// 给 prop 传递一个静态值 |
<div id="blog-post-demo"> | |
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title"></blog-post> | |
</div> | |
// 给 prop 传递一个动态值 | |
Vue.component('blog-post', {props: ['likes','title'], | |
template: '<div><strong>{{likes}}</strong><span>{{title}}</span></div>' | |
}) | |
new Vue({el: '#prop-demo'}) | |
new Vue({ | |
el: '#blog-post-demo', | |
data: { | |
posts: [{likes:'1',title: 'My journey with Vue'}, | |
{likes:'2',title: 'Blogging with Vue'}, | |
{likes:'3',title: 'Why vue is so fun'} | |
] | |
} | |
}) |
<div id="blog-post-demo"> | |
<blog-post v-for="post in posts" v-bind:post="post" v-bind:key="post.id" ></blog-post> | |
</div> | |
Vue.component('blog-post', {props: ['post'], | |
template: '<div><span>{{post.title}}</span></div>' | |
}) | |
// 接受一个单独的 post prop | |
new Vue({ | |
el: '#blog-post-demo', | |
data: { | |
posts: [{id:'1',title: 'My journey with Vue'}, | |
{id:'2',title: 'Blogging with Vue'}, | |
{id:'3',title: 'Why vue is so fun'} | |
] | |
} | |
}) |
传入一个数字:用 v-bind 绑定
<blog-post v-bind:likes="42"></blog-post>
传入一个布尔值:用 v-bind 绑定
<blog-post v-bind:is-published="false"></blog-post>
传入一个数组:用 v-bind 绑定
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
传入一个对象:用 v-bind 绑定
<blog-post | |
v-bind:author="{ | |
name: 'Veronica', | |
company: 'Veridian Dynamics' | |
}" | |
></blog-post> |
传入一个对象的所有属性:可以用一个不带参数的 v -bind 来绑定
<blog-post v-bind="post"></blog-post> | |
post: { | |
id: 1, | |
title: 'My Journey with Vue' | |
} |
单项数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。
监听子组件事件
<!-- 监听子组件事件 --> | |
<div id="blog-posts-events-demo"> | |
<div :style="{fontSize: postFontSize +'em'}"> | |
<blog-post | |
v-for="post in posts" | |
v-bind:key="post.id" | |
v-bind:post="post" | |
v-on:enlarge-text="postFontSize += 0.1" | |
></blog-post> | |
</div> | |
</div> | |
Vue.component('blog-post', {props: ['post'], | |
template: ` | |
<div class="blog-post"> | |
<h3>{{post.title}}</h3> | |
<button v-on:click="$emit('enlarge-text')"> | |
Enlarge text | |
</button> | |
<div v-html="post.content"></div> | |
</div> | |
` | |
}) | |
new Vue({ | |
el: '#blog-posts-events-demo', | |
data: {posts: [{title:'I want to be bigger'}], | |
postFontSize: 1 | |
} | |
}) |
使用 $emit 抛出一个值,在组件上使用 v -model
接受一个 value 属性。再有新的 value 时触发 input 事件
<!-- 在组件上使用 v -model--> | |
<div id="model-demo"> | |
{{value}} | |
<my-com v-model="value"></my-com> | |
<button @click="valueMinus">-1</button> | |
</div> | |
Vue.component('my-com', { | |
props:{ | |
value:{type:Number} | |
}, | |
template: '<div>{{currentValue}}<button @click="handleClick">+1</button></div>', | |
data: function () { | |
return {currentValue: this.value} | |
}, | |
watch: {value(val) {this.currentValue = val;} | |
}, | |
methods: {handleClick: function () { | |
this.currentValue++; | |
this.$emit('input', this.currentValue); | |
} | |
} | |
}) | |
new Vue({ | |
el: '#model-demo', | |
data: {value: 1}, | |
methods:{valueMinus:function () {return this.value--} | |
} | |
}) |
将原生的事件绑定到组件中,需要加上.native
.sync 修饰符
通过插槽分发内容
Slot 是父组件与子组件的通讯方式,可以将父组件的 内容 显示在子组件中。
demo 实例
<div id="slot-demo"> | |
<say-to pname="Kayee"> | |
欢迎学习 vue.js 的 组件之 slot 插槽。</say-to> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> | |
<script> | |
Vue.component('say-to',{props:['pname'], | |
template:'<div>' + | |
'您好 <strong>{{pname}}</strong>' + | |
'<slot></slot>' + | |
'</div>' | |
}) | |
new Vue({el:'#slot-demo',}) | |
</script> |
Slot 组合
<div id="slot-couple"> | |
<welcome a="嘉仪"> | |
<<span slot="Kayee">Kayee</span> | |
<<span slot="Crystal">Crystal</span> | |
<<span slot="Jiayi">Jiayi</span> | |
</welcome> | |
</div> | |
Vue.component('welcome',{props: ['a'], | |
template: '<div>' + | |
'<p> 您好{{a}}</p>'+ | |
'<p> 您好 <slot name="Kayee"></slot> </p>'+ | |
'<p> 您好 <slot name="Crystal"></slot> </p>'+ | |
'<p> 您好 <slot name="Jiayi"></slot> </p>'+ | |
'</div>' | |
}) | |
new Vue({el:'#slot-couple'}) |
具名插槽
<div id="app"> | |
<base-layout> | |
<template v-slot:header> | |
This is hedaer. | |
</template> | |
<template v-slot:main> | |
This is main. | |
</template> | |
<template v-slot:footer> | |
This is footer. | |
</template> | |
</base-layout> | |
</div> | |
Vue.component('base-layout',{ | |
template:'<div class="container">\n' + | |
'<header>\n' + | |
'<slot name="header"></slot>\n' + | |
'</header>\n' + | |
'<main>\n' + | |
'<slot name="main"></slot>\n' + | |
'</main>\n' + | |
'<footer>\n' + | |
'<slot name="footer"></slot>\n' + | |
'</footer>\n' + | |
'</div>' | |
}) |
name 用来定义额外的插槽,一个不带 name 的 <slot> 出口会带有隐含的名字“default”(即为默认的插槽)。<template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
与已经废弃的 slot 特性不同,新版本使用 v -slot: 来代替 slot=” “
注意 v-slot 只能添加在 一个 <template> 上 (只有一种例外情况),这一点和已经废弃的 slot 特性不同。
后备内容
Vue.component('submit-button',{ | |
template:'<button type="submit">\n' + | |
'<slot>Submit</slot>\n' + | |
'</button>' | |
}) | |
<submit-button></submit-button> // 按钮内文字为 Submit | |
<submit-button>POST</submit-button> // 按钮内文字为 POST |
作用域插槽
<div id="app2"> | |
<comp v-slot="user"> | |
{{user.username}} | |
</comp> | |
</div> | |
var CompA =Vue.component('comp', { | |
template:'<div>' + | |
'<slot :username="usernameA"></slot>' + | |
'</div>', | |
data(){ | |
return {usernameA:'Kayee'} | |
} | |
}) |
子组件中的 usernameA 传到父组件中,绑定在 <slot> 元素上的特性被称为插槽 prop
插槽的缩写 v-slot:xxx 等价于 #xxx
动态插槽名 v -slot:[dynamicSlotName]
动态组件
<div id="app1"> | |
<div id="title"> | |
<button @click="changeTab('tab1')">Tab01</button> | |
<button @click="changeTab('tab2')">Tab02</button> | |
</div> | |
<keep-alive> | |
<component v-bind:is="currentTab"></component> | |
</keep-alive> | |
</div> | |
Vue.component('tab1',{data:function () { | |
return{count:0} | |
}, | |
template:'<div class="tab">' + | |
'{{count}}' + | |
'<button @click="addCounter">+1</button>' + | |
'</div>', | |
methods: {addCounter(){this.count++} | |
} | |
}) | |
Vue.component('tab2',{data:function () { | |
return{count:100} | |
}, | |
template:'<div class="tab">' + | |
'{{count}}' + | |
'<button @click="subCounter">-1</button>' + | |
'</div>', | |
methods: {subCounter(){this.count--} | |
} | |
}) | |
new Vue({ | |
el:'#app1', | |
data:{currentTab:'tab1'}, | |
methods:{changeTab(tabName){this.currentTab = tabName} | |
} | |
}) |
1. 关于 is 特性来切换 tab 组件
2.keep-alive 来使数据得到缓存记录
异步组件
Vue.component('async-example',function (resolve,reject) {setTimeout(function () { | |
// 向 `resolve` 回调传递组件定义 | |
resolve({template:'<div>I am async!</div>'}) | |
},1000) | |
}) |
解析 DOM 模板时的注意事项