组件基础
Vue 组件的示例:
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
组件的复用
你可以将组件进行任意次数的复用:
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
互不影响
组件注册
全局注册
Vue.component('my-component-name', {
// ... 选项 ...
})
注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
局部注册
使用components可以局部注册组件,注册后的组件只有在该实例作用域下有效
在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
然后在 components 选项中定义你想要使用的组件:
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
注意局部注册的组件在其子组件中不可用。
Vue组件的模板在某些情况下会受到HTML的影响,例如<table>
内只允许<tr>,<td>
等,可以使用is属性来挂载组件
<div id="app">
<table>
<tbody is="my-component"><.tbody>
</table>
</div>
<script>
Vue.component('my-component', {
template: '<div>这里是组件的内容</div>'
});
var app = new Vue({
el: '#app'
})
</script>
组件和实例的区别在data必须是函数,将数据return出来
Prop
示例:
<div id="app">
<div>
<input v-model="parentMsg">
<br>
<child v-bind:message="parentMsg"></child>
</div>
</div>
<script>
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 “this.message” 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app',
data: {
parentMsg: '父组件内容'
}
})
</script>
注意props后的变量格式:['message']
camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
prop 可以通过 v-bind 动态赋值
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
传入一个对象的所有 property
使用不带参数的 v-bind 将一个对象的所有 property 都作为 prop 传入。例如,对于一个给定的对象 post:
post: {
id: 1,
title: 'My Journey with Vue'
}
下面的模板:
<blog-post v-bind="post"></blog-post>
单向数据流
级 prop 的更新会向下流动到子组件中,但是反过来则不行。防止从子组件意外变更父级组件的状态。
每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。
两种试图变更一个 prop 的情形:
- 父组件传递初始值进来,子组件将其作为初始值保存起来,在自己的作用域下随意使用和修改,这种情况将组件data内再申明一个数据,引用父组件的prop:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
- prop 作为被转变的原始值传入。使用计算属性就可以了:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
Prop 验证
定制 prop 的验证方式,可以为 props 中的值提供一个带有验证需求的对象
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
}
}
}
})
组件通信
子组件用$emit()
来触发事件,父组件用$on()
来监听子组件的事件
$emit()
例子
父组件也可以直接在子组件的自定义标签上用v-on来监听子组件出发的自定义事件
可以在自定义组件上使用v-model命令
v-model还可以用来创建自定义的表单输入组件,进行数据双向绑定,例如:
<div id="app">
<p>总数:{{total}}</p>
<my-component v-model="total"></my-component>
<button @click="handleReduce">-1</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
props:['value'],
template:'<input :value="value" @input="updateValue">',
methods:{
updateValue: function(event){
this.$emit('input', event.target.value);
}
}
});
var app = new Vue({
el: '#app',
data:{
total:0
},
methods:{
handleReduce:function(){
this.total--;
}
}
})
</script>
动态组件 & 异步组件
在一个多标签的界面中使用 is 来切换不同的组件:
<component v-bind:is="currentTabComponent"></component>
使用<keep-alive>
将动态组件包裹,从而在第一次创建组件时把它们缓存下来
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
注意这个 <keep-alive>
要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册。
异步组件
要将应用分割成小的代码块,在需要时从服务器加载模块,可以使用工厂函数定义组件。只有在组件被渲染时,Vue才会触发,然后缓存结果供以后使用
如:
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
工厂函数会收到一个resolve回调,改回调函数在从服务器得到组件定义时被调用、也可以调用reject来表示加载失败
其中函数的内部内容根据需要编写,这里推荐的做法是将异步组件和wepack的code-splitting功能一起配合使用
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
也可以在工厂函数中返回一个Promise,所以可以这样动态导入
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
当使用局部注册的时候,可以直接提供一个返回 Promise 的函数:
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
访问根实例
根实例可以通过 $root 进行访问
例如:
// Vue 根实例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
小型项目中可以用,大项目推荐是用vuex来进行管理
访问子组件实例或子元素
通过ref为子组件赋予一个id
<base-input ref="usernameInput"></base-input>
使用$ref来访问
this.$refs.usernameInput
$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——应该避免在模板或计算属性中访问 $refs。