一、注册组件
Vue.component('my-component-name', { / ... / })
组件名称
在组件的祖册中,第一个参数为组件的名称。
命名方案:
- 串联式命名
- 驼峰式命名
在引用其自定义元素时,两种方案都可以使用。但直接在DOM中引用自定义元素时串联式命名时唯一有效的方式。
全局注册方式
全局注册的组件可以在之后通过new Vue创建的Vue根实例的模板中引用。
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模块,则类似这样
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
)
})
二、props
用于父组件对子组件传递信息。
命名方案
HTML 属性名称对大小写不敏感,因此浏览器会将所有大写字符解释为小写字符。
当在DOM 模板中书写 prop 时,应当将驼峰式转写为等价的串联式。
如果是在使用字符串模板的场景,则没有这些限制。
Vue.component('blog-post', {
// 在 JavaScript 中使用驼峰式(camelCase)
props: ['postTitle'],
template: '{
{ postTitle }}
'
})
传递静态props字符串
静态Prop通过为子组件在父组件中的占位符添加特性的方式来达到传值的目的
除初始值外,父组件的值无法更新到子组件中。
2、定义一个计算属性,处理 prop 的值并返回
由于子组件使用的是计算属性,所以,子组件的数据无法手动修改。
3、更加妥帖的方案是,使用变量储存prop的初始值,并使用watch来观察prop的值的变化。发生变化时,更新变量的值
prop验证
可以为组件的 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不是数字,则抛出警告
传入数字123时,则无警告提示。传入字符串'123'时,就有警告。
将上面代码中,子组件的内容修改如下,可自定义验证函数,当函数返回为false时,则输出警告提示
var childNode = {
template: '{
{message}}',
props:{
'message':{
validator: function (value) {
return value > 10
}
}
}
}
在父组件中传入msg值为1,由于小于10,则输出警告提示
var parentNode = {
template: `
`,
components: {
'child': childNode
},
data(){
return{
msg:1
}
}
};
三、自定义事件
父组件使用props传递数据给子组件,子组件怎么跟父组件通信呢?这时,Vue的自定义事件就派上用场了。
事件绑定
每个 Vue 实例都实现了事件接口 (Events interface),即
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。
// 自定义事件
var childNode5 = {
template:`
`,
data(){
return{
counter:0
}
},
methods:{
incrementCounter(){
this.counter++;
this.$emit('increment');
}
}
}
var parentNode5 = {
template:`
{
{total}}
`,
data(){
return{
'total':0
}
},
methods:{
incrementTotal(){
this.total++;
}
},
components:{
'child':childNode5
}
}
var app5 = new Vue({
el:'#app5',
components:{
'parent5':parentNode5
}
})
命名约定
自定义事件的命名约定与组件注册及props的命名约定都不相同,由于自定义事件实质上也是属于HTML的属性,所以其在HTML模板中,最好使用中划线形式
而子组件中触发事件时,同样使用中划线形式
this.$emit('pass-data',this.childMsg)
数据传递
子组件通过$emit可以触发事件,第一个参数为要触发的事件,第二个事件为要传递的数据
父组件通过$on监听事件,事件处理函数的参数则为接收的数据
// 数据传递
var childNode6 = {
template:`
子组件数据
{
{childMsg}}
`,
data(){
return{
childMsg:''
}
},
methods:{
data(){
this.$emit('pass-data',this.childMsg)
}
}
}
var parentNode6 = {
template:`
父组件数据
{
{msg}}
`,
components:{
'child':childNode6
},
data(){
return{
'msg':'August'
}
},
methods:{
getData(value){
this.msg = value;
}
}
}
var app6 = new Vue({
el:'#app6',
components:{
'parent6':parentNode6
}
})
修改子组件中的input值,则父组件到接收到相同值,则显示出来
sync修饰符
在一些情况下,可能会需要对一个 prop 进行双向绑定。事实上,这正是Vue1.x中的 .sync修饰符所提供的功能。当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定的值。这很方便,但也会导致问题,因为它破坏了单向数据流的假设。
由于子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,完全不知道它何时悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本,上面所说的正是在 2.0 中移除 .sync 的理由
从 2.3.0 起重新引入了 .sync 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 侦听器。
会被扩展为
bar = val">
当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:
this.$emit('update:foo', newValue)
因此,可以使用.sync来简化自定义事件的操作,实现子组件向父组件的数据传递
四、slot
为了让组件可以组合,需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发。
Vue实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的 元素作为原始内容的插槽。
编辑作用域
在深入内容分发API之前,先明确内容在哪个作用域里编译。假定模板为
{
{ message }}
message应该是绑定到父组件的数据还是子组件的数据?答案是父组件。
组件作用域简单地来说就是:父组件模板的内容在父组件作用域内编译,子组件模板的内容在子组件作用域内编译。
一个常见的错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:
如果要绑定作用域内的指令到一个组件的根节点,应当在组件自己的模板上做:
Vue.component('child-component', {
// 有效,因为是在正确的作用域内
template: 'Child',
data: function () {
return {
someChildProperty: true
}
}
})
类似的,分发内容是在父作用域内编译。
默认丢弃
一般地,如果子组件模板不包含
内联模板
如果子组件有 inline-template 特性,组件将把它的内容当作它的模板,而忽略真实的模板内容
但是 inline-template 让模板的作用域难以理解
var childNode = {
template: `
子组件
`,
};
var parentNode = {
template: `
父组件
测试内容
`,
components: {
'child': childNode
},
};
匿名slot
当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身
var childNode = {
template: `
子组件
`,
};
var parentNode = {
template: `
父组件
测试内容
`,
components: {
'child': childNode
},
};
如果出现多于1个的匿名slot,vue将报错
默认值
最初在
当slot存在默认值,且父元素在
var childNode = {
template: `
子组件
我是默认值
`,
};
var parentNode = {
template: `
父组件
`,
components: {
'child': childNode
},
};
当slot存在默认值,且父元素在
var childNode = {
template: `
子组件
我是默认值
`,
};
var parentNode = {
template: `
父组件
我是设置值
`,
components: {
'child': childNode
},
};
具名slot
多个 slot 可以有不同的名字。
具名 slot 将匹配内容片段中有对应 slot 特性的元素
var childNode = {
template: `
子组件
头部默认值
主体默认值
尾部默认值
`,
};
var parentNode = {
template: `
父组件
我是头部
我是尾部
`,
components: {
'child': childNode
},
};
仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的备用插槽。
匿名slot只能作为没有slot属性的元素的插槽,有slot属性的元素如果没有配置slot,则会被抛弃。
var parentNode = {
template: `
父组件
我是主体
我是其他内容
我是尾部
`,
components: {
'child': childNode
},
};
var parentNode = {
template: `
父组件
我是主体
我是其他内容
我是尾部
`,
components: {
'child': childNode
},
};
插入 我是其他内容 子组件 父组件 我是主体 我是其他内容 我是尾部 我是其他内容 都被抛弃 作用域插槽是一种特殊类型的插槽,用作使用一个 (能够传递数据到) 可重用模板替换已渲染元素。 在子组件中,只需将数据传递到插槽,就像将 props 传递给组件一样 在父级中,具有特殊属性 scope 的 元素必须存在,表示它是作用域插槽的模板。scope 的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象 子组件 父组件 hello from parent {
{ props.xxx }} 如果渲染以上结果,得到的输出是 列表组件 父组件var childNode = {
template: `
作用域插槽
var childNode = {
template: `
作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项var childNode = {
template: `
`,
data(){
return{
items:[
{id:1,text:'第1段'},
{id:2,text:'第2段'},
{id:3,text:'第3段'},
]
}
}
};
var parentNode = {
template: `