Vue.component('my-component-name', { / ... / })javascript
在组件的祖册中,第一个参数为组件的名称。vue
命名方案:java
在引用其自定义元素时,两种方案均可以使用。但直接在DOM中引用自定义元素时串联式命名时惟一有效的方式。webpack
全局注册的组件能够在以后经过new Vue建立的Vue根实例的模板中引用。web
Vue.component('my-component-name', { // ... options ... })
先将组件定义为纯JavaScript对象。正则表达式
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }
而后在建立Vue根实例的时候,components选项中定义所须要用到的组件。
对于components对象的每一个属性,对象的key是自定义元素的名称,而value包含着组件的选项对象。数组
new Vue({ el:'#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
局部注册的组件在子组件中没法访问,若是想在ComponentB中访问ComponentA,则应该浏览器
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, ...... }
若是使用ES2015模块,则相似这样app
import ComponentA from './ComponentA.vue' export default { components: { ComponentA }, // ... }
建议建立一个component目录,每一个组件都定义在这个文件中。
在局部注册这些组件以前,须要预先导入每一个须要的组件。函数
import ComponentA from './ComponentA' import ComponentC from './ComponentC' export default { components: { ComponentA, ComponentC }, // ... }
这样就能够在componentB组件的模板内部引用ComponentA和ComponentB了。
基本组件:许多相对通用的组件(内部可能只含有一个 input 或 button 元素)而且每每在其余组件中频繁使用的一类组件。
这致使的结果是许多组件会列出很长的基础组件清单,而后在components选项中进行逐个引用:
import BaseButton from './BaseButton.vue' import BaseIcon from './BaseIcon.vue' import BaseInput from './BaseInput.vue' export default { components: { BaseButton, BaseIcon, BaseInput } }
若是使用 webpack(或者内置 webpack 的 Vue CLI 3+),就能够只经过 require.context 来全局注册这些经常使用基础组件。
在应用程序入口文件(例如 src/main.js)中,经过全局方式导入基础组件。
全局注册方式必须在Vue根实例建立以前置入组件。
import Vue from 'vue' import upperFirst from 'lodash/upperFirst' import camelCase from 'lodash/camelCase' const requireComponent = require.context( // components 文件夹的相对路径 './components', // 是否查找子文件夹 false, // 用于匹配组件文件名的正则表达式 /Base[A-Z]\w+\.(vue|js)$/ ) requireComponent.keys().forEach(fileName => { // 获取组件配置 const componentConfig = requireComponent(fileName) // 取得组件的 Pascal 式命名 const componentName = upperFirst( camelCase( // 将文件名前面的 `'./` 和扩展名剥离 fileName.replace(/^\.\/(.*)\.\w+$/, '$1') ) ) // 以全局方式注册组件 Vue.component( componentName, // 若是组件是经过 `export default` 导出, // 则在 `.default` 中,查找组件选项, // 不然回退至模块根对象中,查找组件选项 componentConfig.default || componentConfig ) })
用于父组件对子组件传递信息。
HTML 属性名称对大小写不敏感,所以浏览器会将全部大写字符解释为小写字符。
当在DOM 模板中书写 prop 时,应当将驼峰式转写为等价的串联式。
若是是在使用字符串模板的场景,则没有这些限制。
Vue.component('blog-post', { // 在 JavaScript 中使用驼峰式(camelCase) props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' }) <!-- 在 HTML 中使用串联式(kebab-case) --> <blog-post post-title="hello!"></blog-post>
静态Prop经过为子组件在父组件中的占位符添加特性的方式来达到传值的目的
<div id="example"> <parent></parent> </div> <script type="text/javascript"> var childNode = { template:` <div>{{ message }}</div>`, props: ['message'] } var parentNode = { template:` <div class="parent"> <child message="aaa"></child> <child message="bbb"></child> </div>`, components: { 'child':childNode } } var app1 = new Vue({ el: '#example', components: { 'parent':parentNode } })
经过v-bind给props分配动态的值,每当父组件的数据变化时,该变化也会传导给子组件:
<div id="app2"> <parent2></parent2> </div> // 动态prop var childNode2 ={ template:`<div>{{myMessage}}</div>`, props:['myMessage'] } var parentNode2 ={ template:` <div> <child :myMessage="data1"></child> <child :myMessage="data2"></child> </div>`, components:{ 'child':childNode2 }, data(){ return{ 'data1':'动态aaa', 'data2':'动态bbb' } } } var app2 = new Vue({ el:'#app2', components:{ 'parent2':parentNode2 } })
在这两个例子中咱们向prop传递的都是字符串,实际上也能够传递任意类型的值。
传递数字时若是使用“1”字面量则传递的是字符串而非数字。
须要使用 v-bind传递一个实际的 number,从而让它的值被看成JS表达式计算.
<!--传递数值--> <div id="app3"> <my-parent></my-parent> </div> // 传递数值 var childNode3 = { template:`<div>{{myMessage}}的类型是{{type}}</div>`, props:['myMessage'], computed:{ type(){ return typeof this.myMessage } } } var parentNode3 = { template:` <div> <my-child :my-message="1"></my-child> </div>`, components:{ 'myChild':childNode3 } } var app3 = new Vue({ el:'#app3', components:{ 'MyParent':parentNode3 } })
或者可使用动态props,在data属性中设置对应的数字1。
<!-- 没有设定值得属性默认值为‘true’ --> <blog-post favorited></blog-post> <!-- `false` 是静态的,这就须要咱们使用 v-bind, --> <!-- 来告诉 Vue 它是以 JavaScript 表达式表现,而不是一个字符串 --> <base-input v-bind:favorited="false"> <!-- 将一个变量,动态地分配到属性值上 --> <base-input v-bind:favorited="post.currentUserFavorited">
<!-- array 是静态的,这就须要咱们使用 v-bind, --> <!-- 来告诉 Vue 它是以 JavaScript 表达式表现,而不是一个字符串 --> <blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post> <!-- 将一个变量,动态地分配到属性值上 --> <blog-post v-bind:comment-ids="post.commentIds"></blog-post>
<!-- object 是静态的,这就须要咱们使用 v-bind, --> <!-- 来告诉 Vue 它是以 JavaScript 表达式表现,而不是一个字符串 --> <blog-post v-bind:comments="{ id: 1, title: '个人 Vue 旅程' }"></blog-post> <!-- 将一个变量,动态地分配到属性值上 --> <blog-post v-bind:post="post"></blog-post>
post: { id: 1, title: '个人 Vue 旅程' } // 如下模板: <blog-post v-bind="post"></blog-post> // 等价于: <blog-post v-bind:id="post.id" v-bind:title="post.title" ></blog-post>
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,可是不会反过来。这是为了防止子组件无心修改了父组件的状态——这会让应用的数据流难以理解
另外,每次父组件更新时,子组件的全部 prop 都会更新为最新值。这意味着不该该在子组件内部改变 prop。若是这么作了,Vue 会在控制台给出警告
下面是一个典型例子
<!--单向数据流--> <div id="app4"> <parent4></parent4> </div> // 单向数据流 var childNode4 = { template:` <div class="child"> <div> <span>子组件数据</span> <input v-model="childMsg"> </div> <p>{{childMsg}}</p> </div>`, props:['childMsg'] } var parentNode4 = { template:` <div class="parent"> <div> <span>父组件数据</span> <input v-model="Msg"> </div> <p>{{Msg}}</p> <child :child-msg="Msg"></child> </div>`, components:{ 'child':childNode4 }, data(){ return{ 'Msg':'August' } } } var app4 = new Vue({ el:'#app4', components:{ 'parent4':parentNode4 } })
父组件数据变化时,子组件数据会相应变化;而子组件数据变化时,父组件数据不变,并在控制台显示警告。
修改prop中的数据,一般有如下两种缘由
一、prop 做为初始值传入后,子组件想把它看成局部数据来用
二、prop 做为初始值传入,由子组件处理成其它数据输出
[注意]JS中对象和数组是引用类型,指向同一个内存空间,若是 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态
对于这两种状况,正确的应对方式是
一、定义一个局部变量,并用 prop 的值初始化它
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
可是,定义的局部变量counter只能接受initialCounter的初始值,当父组件要传递的值发生变化时,counter没法接收到最新值
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], data(){ return{ temp:this.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>
除初始值外,父组件的值没法更新到子组件中。
二、定义一个计算属性,处理 prop 的值并返回
<script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], computed:{ temp(){ return this.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>
因为子组件使用的是计算属性,因此,子组件的数据没法手动修改。
三、更加妥帖的方案是,使用变量储存prop的初始值,并使用watch来观察prop的值的变化。发生变化时,更新变量的值
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], data(){ return{ temp:this.childMsg } }, watch:{ childMsg(){ this.temp = this.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>
能够为组件的 props 指定验证规格。若是传入的数据不符合规格,Vue会发出警告。当组件给其余人使用时,这颇有用
要指定验证规格,须要用对象的形式,而不能用字符串数组
Vue.component('example', { props: { // 基础类型检测 (`null` 意思是任何类型均可以) 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 value > 10 } } } })
type 能够是下面原生构造器
String Number Boolean Function Object Array Symbol
type 也能够是一个自定义构造器函数,使用 instanceof 检测。
当 prop 验证失败,Vue 会在抛出警告 (若是使用的是开发版本)。props会在组件实例建立以前进行校验,因此在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性还没法使用
下面是一个简单例子,若是传入子组件的message不是数字,则抛出警告
<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>
传入数字123时,则无警告提示。传入字符串'123'时,就有警告。
将上面代码中,子组件的内容修改以下,可自定义验证函数,当函数返回为false时,则输出警告提示
var childNode = { template: '<div>{{message}}</div>', props:{ 'message':{ validator: function (value) { return value > 10 } } } }
在父组件中传入msg值为1,因为小于10,则输出警告提示
var parentNode = { template: ` <div class="parent"> <child :message="msg"></child> </div>`, components: { 'child': childNode }, data(){ return{ msg:1 } } };
父组件使用props传递数据给子组件,子组件怎么跟父组件通讯呢?这时,Vue的自定义事件就派上用场了。
每一个 Vue 实例都实现了事件接口 (Events interface),即
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
父组件能够在使用子组件的地方直接用 v-on 来监听子组件触发的事件。
<!--自定义事件--> <div id="app5"> <parent5></parent5> </div>
// 自定义事件 var childNode5 = { template:` <button @click="incrementCounter">{{counter}}</button>`, data(){ return{ counter:0 } }, methods:{ incrementCounter(){ this.counter++; this.$emit('increment'); } } } var parentNode5 = { template:` <div class="parent"> <p>{{total}}</p> <child @increment="incrementTotal"></child> <child @increment="incrementTotal"></child> </div>`, data(){ return{ 'total':0 } }, methods:{ incrementTotal(){ this.total++; } }, components:{ 'child':childNode5 } } var app5 = new Vue({ el:'#app5', components:{ 'parent5':parentNode5 } })
自定义事件的命名约定与组件注册及props的命名约定都不相同,因为自定义事件实质上也是属于HTML的属性,因此其在HTML模板中,最好使用中划线形式
<child @pass-data="getData"></child>
而子组件中触发事件时,一样使用中划线形式
this.$emit('pass-data',this.childMsg)
子组件经过$emit能够触发事件,第一个参数为要触发的事件,第二个事件为要传递的数据
父组件经过$on监听事件,事件处理函数的参数则为接收的数据
<!--数据传递--> <div id="app6"> <parent6></parent6> </div> // 数据传递 var childNode6 = { template:` <div class="child"> <div> <span>子组件数据</span> <input v-model="childMsg" @input="data"> </div> <p>{{childMsg}}</p> </div>`, data(){ return{ childMsg:'' } }, methods:{ data(){ this.$emit('pass-data',this.childMsg) } } } var parentNode6 = { template:` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child @pass-data="getData"></child> </div>`, components:{ 'child':childNode6 }, data(){ return{ 'msg':'August' } }, methods:{ getData(value){ this.msg = value; } } } var app6 = new Vue({ el:'#app6', components:{ 'parent6':parentNode6 } })
修改子组件中的input值,则父组件到接收到相同值,则显示出来
在一些状况下,可能会须要对一个 prop 进行双向绑定。事实上,这正是Vue1.x中的 .sync修饰符所提供的功能。当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定的值。这很方便,但也会致使问题,由于它破坏了单向数据流的假设。 因为子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,彻底不知道它什么时候悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本,上面所说的正是在 2.0 中移除 .sync 的理由
从 2.3.0 起从新引入了 .sync 修饰符,可是此次它只是做为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 侦听器。
<comp :foo.sync="bar"></comp>
会被扩展为
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件须要更新 foo 的值时,它须要显式地触发一个更新事件:
this.$emit('update:foo', newValue)
所以,可使用.sync来简化自定义事件的操做,实现子组件向父组件的数据传递
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div>子组件数据:{{childMsg}}</div> <input v-model="childMsg"> <button @click=add >+1</button> </div> `, data(){ return{ childMsg: 0 } }, methods:{ add(){ this.childMsg++; this.$emit('update:foo',this.childMsg); } } }; var parentNode = { template: ` <div class="parent"> <p>父组件数据:{{msg}}</p> <child :foo.sync="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':0 } } }; // 建立根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
为了让组件能够组合,须要一种方式来混合父组件的内容与子组件本身的模板。这个过程被称为 内容分发。
Vue实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的 <slot> 元素做为原始内容的插槽。
在深刻内容分发API以前,先明确内容在哪一个做用域里编译。假定模板为
<child-component> {{ message }} </child-component>
message应该是绑定到父组件的数据仍是子组件的数据?答案是父组件。
组件做用域简单地来讲就是:父组件模板的内容在父组件做用域内编译,子组件模板的内容在子组件做用域内编译。
一个常见的错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:
<!-- 无效 --> <child-component v-show="someChildProperty"></child-component>
若是要绑定做用域内的指令到一个组件的根节点,应当在组件本身的模板上作:
Vue.component('child-component', { // 有效,由于是在正确的做用域内 template: '<div v-show="someChildProperty">Child</div>', data: function () { return { someChildProperty: true } } })
相似的,分发内容是在父做用域内编译。
通常地,若是子组件模板不包含<slot>插口,父组件的内容将会被丢弃
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <p>子组件</p> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p>测试内容</p> </child> </div> `, components: { 'child': childNode }, }; // 建立根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
以下图所示,<child>所包含的<p>测试内容</p>被丢弃
若是子组件有 inline-template 特性,组件将把它的内容看成它的模板,而忽略真实的模板内容
可是 inline-template 让模板的做用域难以理解
var childNode = { template: ` <div class="child"> <p>子组件</p> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child inline-template> <p>测试内容</p> </child> </div> `, components: { 'child': childNode }, };
当子组件模板只有一个没有属性的 slot 时,父组件整个内容片断将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签自己
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p>测试内容</p> </child> </div> `, components: { 'child': childNode }, };
若是出现多于1个的匿名slot,vue将报错
默认值
最初在 <slot> 标签中的任何内容都被视为备用内容,或者称为默认值。备用内容在子组件的做用域内编译,而且只有在宿主元素为空,且没有要插入的内容时才显示备用内容
当slot存在默认值,且父元素在<child>中没有要插入的内容时,显示默认值。
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot><p>我是默认值</p></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child></child> </div> `, components: { 'child': childNode }, };
当slot存在默认值,且父元素在<child>中存在要插入的内容时,则显示设置值
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot><p>我是默认值</p></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p>我是设置值</p> </child> </div> `, components: { 'child': childNode }, };
<slot> 元素能够用一个特殊的属性 name 来配置如何分发内容。
多个 slot 能够有不一样的名字。
具名 slot 将匹配内容片断中有对应 slot 特性的元素
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot name="my-header">头部默认值</slot> <slot name="my-body">主体默认值</slot> <slot name="my-footer">尾部默认值</slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p slot="my-header">我是头部</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
仍然能够有一个匿名 slot,它是默认 slot,做为找不到匹配的内容片断的备用插槽。
匿名slot只能做为没有slot属性的元素的插槽,有slot属性的元素若是没有配置slot,则会被抛弃。
var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p slot="my-body">我是主体</p> <p>我是其余内容</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p slot="my-body">我是主体</p> <p>我是其余内容</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
<p slot="my-body">插入<slot name="my-body">中,<p>我是其余内容</p>插入<slot>中,而<p slot="my-footer">被丢弃
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot name="my-body">主体默认值</slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <p slot="my-body">我是主体</p> <p>我是其余内容</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
<p>我是其余内容</p>和<p slot="my-footer">都被抛弃
做用域插槽是一种特殊类型的插槽,用做使用一个 (可以传递数据到) 可重用模板替换已渲染元素。
在子组件中,只需将数据传递到插槽,就像将 props 传递给组件同样
<div class="child"> <slot text="hello from child"></slot> </div>
在父级中,具备特殊属性 scope 的 <template> 元素必须存在,表示它是做用域插槽的模板。scope 的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象
var childNode = { template: ` <div class="child"> <p>子组件</p> <slot xxx="hello from child"></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <template scope="props"> <p>hello from parent</p> <p>{{ props.xxx }}</p> </template> </child> </div> `, components: { 'child': childNode }, };
若是渲染以上结果,获得的输出是
列表组件
做用域插槽更具表明性的用例是列表组件,容许组件自定义应该如何渲染列表每一项
var childNode = { template: ` <ul> <slot name="item" v-for="item in items" :text="item.text">默认值</slot> </ul> `, data(){ return{ items:[ {id:1,text:'第1段'}, {id:2,text:'第2段'}, {id:3,text:'第3段'}, ] } } }; var parentNode = { template: ` <div class="parent"> <p>父组件</p> <child> <template slot="item" scope="props"> <li>{{ props.text }}</li> </template> </child> </div> `, components: { 'child': childNode }, };