系列文章导航
1 组件介绍
组件是Vue.js最强大的功能之一。组件是可复用的Vue实例,且带有一个名字,通过组件封装可重用的代码,在较高层面上,组件是自定义元素。
2 组件注册
1.1 组件注册方式1
组件名对应标签名,标签名不允许出现大写字母,因此组件名包含多个单词时需要采用kebab-case (短横线分隔命名) 命名。
组件名称中如果用了大写字母,使用组件的时候需要转成kebab-case方法,例如组件名为:uniStatusBar,使用组件的时候<uni-status-bar></uni-status-bar>
组件模板代码必须是单个跟元素。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <button-counter>组件1</button-counter> <button-counter>组件2</button-counter> </div> <script type="text/javascript"> // 1、创建组件 var myComponent = Vue.extend({
// 组件内部数据 data:function() {
// data是一个函数 return { count:0 } },
// 组件模板 template:'<button @click="count++">You clicked me {{ count }} times.</button>' }); // 2、注册组件 Vue.component('button-counter',myComponent); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', // vue数据 data: { } }); </script> </body> </html>
1.2 组件注册方式2
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <button-counter>组件1</button-counter> <button-counter>组件2</button-counter> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('button-counter', {
// 组件内部数据 data: function() {
// data是一个函数 return { count: 0 } },
// 组件模板 template: '<button @click="count++">You clicked me {{ count }} times.</button>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', // vue数据 data: {} }); </script> </body> </html>
1.3 组件嵌套注册
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <parent></parent> </div> <script type="text/javascript"> //定义子组件 var child = Vue.extend({ template: '<button @click.stop="subCount++">我是子组件{{subCount}}</button>', data: function() { return { subCount: 1 } } }); // 定义父组件 Vue.component('parent', { data: function() { // 一定要返回数据 return { count: 0 } }, //定义组件内所用到的子组件 components: { 'child':child }, template: '<button @click="count++">我是父组件{{count}} - <child></child></button>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', // vue数据 data: { } }); </script> </body> </html>
两个按钮可以单独点击:子组件事件加了.stop防止冒泡。
运行效果:
3 Props的使用
3.1 通过Props公开组件属性
属性名对应标签属性,标签的属性名不允许出现大写字母,因此属性名包含多个单词时需要采用kebab-case (短横线分隔命名) 命名。
Props公开的属性是父组件传递给子组件的,当父组件数据发生变更的时候子组件一起变更,因此在子组件内不要对传入的数据变量进行修改。
当父组件需要向子组件传递数据时,需要在子组件内通过props向外公开接收的属性列表。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <!-- 生成组件 --> <button-counter title="组件1">组件1</button-counter> <button-counter title="组件2">组件2</button-counter> <!-- 通过v-for生成多个组件 --> <button-counter v-for="post in posts" :start="post.id" :title="post.title">组件2</button-counter> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('button-counter', { data: function() { // 一定要返回数据 return { count: 0 } }, // 对外公开的属性,也可以用简化方式 props: ['title','start'] props: { title: { type:String, required: true, default: "默认title" }, start:{ type:Number } }, template: '<div><button @click="count++">You clicked {{title}} {{start}} {{ count }} times.</button></div>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', // vue数据 data: { posts: [ {id: 1,title: 'My journey with Vue'}, {id: 2,title: 'Blogging with Vue'}, {id: 3,title: 'Why Vue is so fun'} ] } }); </script> </body> </html>
3.2 Props的方方面面
1) 普通方式
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
2) 类型限定方式
props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor }
3) 复杂限定方式
Vue.component('my-component', { props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 }, // 带有默认值的对象 propE: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } })
4) 单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
4 子组件事件
4.1 通过$emit公开组件事件
$emit参数值中不允许出现大写字母,因此事件名需要采用kebab-case (短横线分隔命名) 命名。
以下两种情况要用到$emit:
1) 当子组件某事件触发时,要执行父组件的某个事件处理方法时
2) 当要把子组件的数据传递给父组件时
采用$emit方式,代码实例如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <!-- 使用组件时 绑定组件对外公开的事件的事件处理方法--> <button-counter @outer-event="receiveSub1">组件1</button-counter> <div>父组件的值:{{pValue}}</div> <button-counter @outer-event="receiveSub2">组件2</button-counter> <div>父组件的值:{{pValue}}</div> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('button-counter', { data: function() { return { count: 0 } }, methods:{ // 组件内的事件处理方法 onClick:function(){ this.count++; // 通过$emit('组件对外公开的事件名'),调用公开事件绑定的事件处理函数,并向外传递数据 this.$emit("outer-event",this.count); } }, template: '<div><button @click="onClick">You clicked {{ count }} times.</button></div>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', // vue数据 data: { pValue:0 }, methods:{ // 组件外的事件处理方法,该方法通过组件内的事件调用 receiveSub1:function(c){ console.info("父组件1的方法被子组件调用了"+c); // 变更父组件的值 this.pValue = c; }, receiveSub2:function(c){ console.info("父组件2的方法被子组件调用了"+c); this.pValue = c; } } }); </script> </body> </html>
两个子组件共享父组件的值,当单击第一个组组件4次后,运行效果如下:
核心代码
子组件方法内:this.$emit('自定义事件', data.ids) 组件使用时:<组件名 @自定义事件='父组件的方法'/>
4.2 在组件上使用v-model
v-model是Props和事件的结合,用来实现父组件和子组件的双向通信。
一个组件上的 v-model
默认会利用名为 value
的 props和名为 input
的事件,为了防止冲突,也可以通过组件的model选项来自己定义。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <!-- 使用组件时 绑定组件对外公开的事件的事件处理方法--> <button-counter v-model="pValue">组件1</button-counter> <div>父组件的值:{{pValue}}</div> <button-counter>组件2</button-counter> <div>父组件的值:{{pValue}}</div> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('button-counter', { data: function() { return { count: 0 } }, props:{ value_me:{ default:"" } }, // 通过model声明 // v-model所绑定变量的值会传递给子组件的value_me变量 // 子组件对开公开的事件input_me,通过input_me事件可以实现向外层v-model绑定变量传值 model:{ prop:'value_me', event:'input_me' }, methods:{ onChange:function(e){ console.info(e.target.value); // 外部不需要提供方法绑定子组件的事件,$emit的第二个参数值会传递给v-model所绑定的变量 this.$emit('input_me',e.target.value); } }, template: '<div><input type="text" :value="value_me" @change="onChange($event)" /><br/><span>子组件内的值:{{value_me}}</span></div>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', // vue数据 data: { pValue:"" } }); </script> </body> </html>
4.3 将原生组件绑定到组件上
在组件使用的时候,直接在组件标签上绑定原生事件是不生效的,如果要把该原生事件绑定到组件内的某个表单上,需要在表单上增加 v-on="$listeners" 属性值。
直接在组件内的input上绑定focus也可以的,不清楚该语法使用场景是啥。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <!-- 使用组件时 绑定组件对外公开的事件的事件处理方法--> <button-counter v-model="pValue" @focus="onFocus">组件1</button-counter> <div>父组件的值:{{pValue}}</div> <button-counter>组件2</button-counter> <div>父组件的值:{{pValue}}</div> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('button-counter', { data: function() { return { count: 0 } }, props: { value_me: { default: "" } }, // 通过model声明 // v-model所绑定变量的值会传递给子组件的value_me变量 // 子组件对开公开的事件input_me,通过input_me事件可以实现向外层v-model绑定变量传值 model: { prop: 'value_me', event: 'input_me' }, methods: { onChange: function(e) { console.info(e.target.value); // 外部不需要提供方法绑定子组件的事件,$emit的第二个参数值会传递给v-model所绑定的变量 this.$emit('input_me', e.target.value); } }, template: '<div><input type="text" :value="value_me" @change="onChange($event)" v-on="$listeners" /><br/><span>子组件内的值:{{value_me}}</span></div>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', // vue数据 data: { pValue: "" }, methods: { onFocus: function() { console.info("触发onFocus"); this.pValue="获取光标了" } } }); </script> </body> </html>
5 插槽分发内容
在组件模板代码中可以创建插糟(占位符),在组件使用的时候,可以向组件内部的插槽填充内容,实现组件展示内容的个性化定制。
5.1 不具名slot插槽
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <!-- 使用组件时 绑定组件对外公开的事件的事件处理方法--> <my-component></my-component> <my-component>父容器填充内容</my-component> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('my-component', { template: '<div><slot>第一个插槽默认内容</slot><button>子组件<slot>第二个插槽默认内容</slot></button><slot>第三个插槽默认内容</slot></div>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app' }); </script> </body> </html>
使用组件是,组件内的内容自动填充到所有不具名的插槽中,可以有多个不具名插糟,运行效果:
5.2 具名slot插槽
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <!-- 使用组件时 绑定组件对外公开的事件的事件处理方法--> <my-component><span slot="begin">父容器给begin的内容{{pValue}}</span>默认填充内容</my-component> <my-component><span slot="end">父容器给end的内容</span>父容器填充内容</my-component> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('my-component', { data:function() { return { cValue:"4567" } }, template: '<div><slot name="begin">begin插槽默认内容</slot><button>子组件<slot>center插槽默认内容</slot></button><slot name="end">end插槽默认内容</slot></div>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', data:{ // 外部组件的变量 pValue:"1234" } }); </script> </body> </html>
运行效果:
填充内容里边可以使用外层组件的数据,不能使用组件内的数据。
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
v2.6.0之后,用v-slot替代了slot和slot-scope这两个指令。
通过v-slot设定插槽名称,v-slot必须应用在template标记上,修改后代码如下:
<my-component><template v-slot="begin">父容器给begin的内容{{pValue}}</template>默认填充内容</my-component>
<my-component><template v-slot="end">父容器给end的内容</template>父容器填充内容</my-component>
5.3 让父级代码中使用组件内的数据
有时让插槽内容能够访问子组件中才有的数据是很有用的。
实例代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <!-- 使用组件时 绑定组件对外公开的事件的事件处理方法--> <my-component><template v-slot="begin">父给begin的内容{{pValue}}}</template>默认填充内容</my-component> <!-- 通过v-slot:插糟名="引用分享数据的变量名",名称可以随便起 --> <my-component><template v-slot:begin="slotProps">{{slotProps.cValue}}-{{slotProps.user.firstName}}</template></my-component> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('my-component', { data: function() { return { cValue: "内部变量", user:{ firstName:'wang', lastName:'zhirui' } } }, // 对应插糟上一定要通过v-bind:对外属性名,或者简写方式 :对外属性名,公开要对外分享得内部数据 template: '<div><slot name="begin" v-bind:cValue="cValue" :user="user">begin默认内容</slot><button>子组件<slot>center默认内容</slot></button><slot name="end">end默认内容</slot></div>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', data: { // 外部组件的变量 pValue:"外部变量" }, created:function(){ console.info("12121") } }); </script> </body> </html>
那个插槽分享出去的,那个插槽才可以使用
运行效果:
5.4 v-slot的相关情况
1)独占默认插槽的缩写语法:废弃template标签直接加载组件上
<current-user v-slot:default="slotProps"> {{ slotProps.user.firstName }} </current-user>
<current-user v-slot="slotProps"> {{ slotProps.user.firstName }} </current-user>
2)名称直接带表达式
<current-user v-slot="{ user }"> {{ user.firstName }} </current-user>
3)名称重命名
<current-user v-slot="{ user: person }"> {{ person.firstName }} </current-user>
4)变更属性内容
<current-user v-slot="{ user = { firstName: 'Guest' } }"> {{ user.firstName }} </current-user>
5.5 动态插槽名
变量名不能有大写字母。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="vue.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="app"> <!-- 使用组件时 绑定组件对外公开的事件的事件处理方法--> <my-component><template v-slot="begin">父给begin的内容{{pValue}}}</template>默认填充内容</my-component> <!-- 通过v-slot:插糟名="公开分享数据的引用变量名" --> <my-component><template v-slot:[slotname]="slotProps">{{slotProps.cValue}}-{{slotProps.user.firstName}}</template></my-component> </div> <script type="text/javascript"> // 1、定义和注册合并 Vue.component('my-component', { data: function() { return { cValue: "内部变量", user:{ firstName:'wang', lastName:'zhirui' } } }, // 对应插糟上一定要通过v-bind:对外属性名,或者简写方式:对外属性名,公开要对外分享得内部数据 template: '<div><slot name="begin" v-bind:cValue="cValue" :user="user">begin默认内容</slot><button>子组件<slot>center默认内容</slot></button><slot name="end">end默认内容</slot></div>' }); var app = new Vue({ // 挂载元素 - 作用范围 el: '#app', data: { // 外部组件的变量 pValue:"外部变量", slotname:"begin" }, created:function(){ console.info("12121") } }); </script> </body> </html>
5.5 具名插槽的缩写
v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
。例如 v-slot:header
可以被重写为 #header
: