组件
组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以is
特性扩展。
使用组件
- 注册一个全局组件,你可以使用
Vue.component(tagName, [definition])
// 注册组件,传入一个扩展过的构造器 Vue.component('my-component', Vue.extend({ /* ... */ })) // 注册组件,传入一个选项对象 (自动调用 Vue.extend) Vue.component('my-component', { /* ... */ }) // 获取注册的组件 (始终返回构造器) var MyComponent = Vue.component('my-component') - 使用组件实例选项注册局部组件 new Vue({ // ... components: { // <my-component> 将只在父模板可用 'my-component': Child } })
DOM模板解析说明
Vue 只有在浏览器解析和标准化 HTML 后才能获取模板内容。像 <ul>
,<ol>
,<table>
,<select>
限制了能被它包裹的元素,或者像 <option>
这样的元素只能出现在某些其它元素内部。在自定义组件中使用会导致一些问题。
<table>
<!-- 被认为是无效的内容,因此在渲染的时候会导致错误 -->
<my-row>...</my-row>
<!-- 使用特殊的 is 属性 -->
<tr is="my-row"></tr>
</table>
应当注意,下述情况不受限制:
<script type="text/x-template">
<script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> </script> Vue.component('hello-world', { template: '#hello-world-template' })- JavaScript 内联模板字符串 <my-component inline-template> <div> <p>These are compiled as the component's own template.</p> <p>Not parent's transclusion content.</p> </div> </my-component>
.vue
组件
注意,使用上述三种方式不会报错,单不能渲染到指定位置。is方式是可行的!
完整参考示例:https://jsfiddle.net/381510688/1LasaLhL/
data必须是函数
为了隔离作用域!
代码语言:javascript复制<div id="example">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
代码语言:javascript复制var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter = 1">{{ counter }}</button>',
// 技术上 data 的确是一个函数了,因此 Vue 不会警告,
// 但是我们返回给每个组件的实例却引用了同一个 data 对象
data: function () {
return data
}
})
new Vue({
el: '#example'
})
上述需要修改为
代码语言:javascript复制data: function () {
return {
counter: 0
}
}
组件之间的通信
父组件=>子组件通信
props down, events up
组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的 props 选项。
父组件通过props来给子组件传递数据,子组件需要显示的用props选项声明props。可以通过v-bind动态的绑定props的值到父组件的数据中,每次当绑定的数据在父组件中发生改变的时候,该组件也会相应的传递给子组件。需要注意的是,要使用v-bind这样传递下去的才是正真的字面量,否则都会当做字符串(下述age1是string,age2为number)!
代码语言:javascript复制<child name="ligang" age1="28" :age2="28"></child>
Vue.component('child', {
// 子组件显示的声明props
props: ['name', 'age1', 'age2'],
computed: {
ageType() {
return {
age1: typeof this.age1,
age2: typeof this.age2
}
}
},
// prop就像data一样可以再模板中使用也可以通过this来调用
template: '<span> {{ name }} - {{ageType}}</span>'
})
子组件内修改变父组件传递的prop值
prop是单向绑定的,当父组件的属性变化时,将传递给子组件,但是在子组件中改变数据的时候并不会传递给父组件(为了防止子组件无意间修改父组件的状态),所以不应该在子组件中改变prop的数据。如果想在子组件中想改变prop的值通常有二种方式:
- 方式一:作为本地数据的初始值使用 props: ['initialCounter'], data: function () { return { couter: this.initialCounter } }
- 方式二:作为计算属性使用 props: ['size'], computed: { childSize: function () { return this.size.trim().toLowerCase() } }
注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
Prop验证
代码语言:javascript复制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
}
}
}
})
注意 props 会在组件实例创建之前进行校验,所以在 default
或 validator
函数里,诸如 data
、computed
或 methods
等实例属性还无法使用。
非Prop特性
所谓非 prop 特性,就是它可以直接传入组件,而不需要定义相应的 prop。组件可以接收任意传入的特性,这些特性都会被添加到组件的根元素上。
代码语言:javascript复制<my-component data-index="1" class="xx1"></my-component>
- 相同名称的属性,会覆盖组件内部的;
class
和style
特性会做merge操作。
完整参考示例:https://jsfiddle.net/381510688/afxex6vc/
子组件=>父组件通信
子组件通过自定义事件的方法将数据传递给父组件
代码语言:javascript复制<my-component :age="age" @increment="addAag"></my-component>
<script>
const app = new Vue({
el: '#app',
data(){
return {
age: 28
}
},
methods: {
addAag() {
this.age = 1;
}
},
components: {
// 内部组件
'my-component': {
props: ['age'],
methods: {
increment() {
this.$emit('increment');
}
},
template: `
<div>
<p>{{this.age}}-{{this.age2}}</p>
<button @click="increment">增加年龄</button>
</div>
`
}
}
});
</script>
完整示例参考地址:https://jsfiddle.net/381510688/jvhtwc8b/
事件修饰符
.native
修饰符:在某个组件的根元素上监听一个原生事件 <my-component v-on:click.native="doTheThing"></my-component>.sync
修饰符:2.0中移除了.sync
修饰符,但我们经常需要对一个prop进行『双向绑定』,所以在2.3.0进行了重新引入,但是这次它只是作为一个编译时的语法糖存在。 <comp :foo.sync="bar"></comp> <!-- 会被扩展为:--> <comp :foo="bar" @update:foo="val => bar = val"></comp> 当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件: this.$emit('update:foo', newValue)
示例:ElementUI中的el-dialog
代码语言:javascript复制 <el-dialog :visible.sync="dialogVisible">
代码语言:javascript复制 <el-dialog :visible="dialogVisible" :before-close="beforeClose">
第一种写法关闭或是点击空白处无需特别处理,el-dialog组件内部会修改当前值状态,通过.sync修饰符传递给父组件;第二种写法,需要再beforeClose方法内手动处理this.dialogVisible = false
。
剖析el-dialog源码
代码语言:javascript复制 handleClose() {
// el-dialog组件上存在before-close,则先调用beforeClose方法,然后调用this.hide
// 这也是为什么this.beforeClose处理完后,必须调用done(),done实际就是this.hide
if (typeof this.beforeClose === 'function') {
this.beforeClose(this.hide);
} else {
this.hide();
}
},
hide(cancel) {
if (cancel !== false) {
this.$emit('update:visible', false);
this.$emit('visible-change', false);
}
}
完整示例参考地址:https://jsfiddle.net/381510688/vqfoshff/
v-model
前文中已提及,v-mdoel是<input v-bind:value="something" v-on:input="something =$event.target.value">
的语法糖。默认情况下,一个组件的 v-model
会使用 value
属性和 input
事件,但是诸如单选框、复选框之类的输入类型可能把 value
属性用作了别的目的。model
选项可以回避这样的冲突:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
},
// ...
})
代码语言:javascript复制<my-checkbox v-model="foo" value="some value"></my-checkbox>
上述代码等价于:
代码语言:javascript复制<my-checkbox
:checked="foo"
@change="val => { foo = val }"
value="some value">
</my-checkbox>
非父子组件通信
非父子组件的通信如果情况简单,可以使用全局event bus var bus = new Vue()
;复杂的情况下往往用vuex。