创建组件是 Vue 开发的基础技能。看似简单的
Vue.component()调用,背后有很多值得注意的细节。理解这些细节,能帮你避免很多常见问题。
使用 Vue.component() 定义全局组件:
Vue.component('组件名称', {
// 组件选项
template: '...',
data: function() { return { ... } },
methods: { ... }
})
第一个参数是组件名称,第二个参数是组件选项对象。
这是 Vue 组件最重要的规则之一:组件的 data 必须是一个函数。
如果 data 是对象,所有组件实例会共享同一个数据引用:
// ❌ 错误示例
Vue.component('counter', {
data: {
count: 0
},
template: '<button @click="count++">{{ count }}</button>'
})
// 结果:三个按钮共享同一个 count,点击任意一个都会影响其他
使用函数返回数据,每个组件实例都有独立的数据副本:
// ✅ 正确示例
Vue.component('counter', {
data: function() {
return {
count: 0
}
},
template: '<button @click="count++">{{ count }}</button>'
})
// 结果:每个按钮维护自己的 count
Vue 会报错
如果 data 不是函数,Vue 会在控制台警告:
[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
// 标准写法
data: function() {
return {
message: 'Hello'
}
}
// ES6 简写
data() {
return {
message: 'Hello'
}
}
// 使用箭头函数(不推荐,this 指向有问题)
data: () => ({
message: 'Hello'
})
组件模板有多种定义方式,各有优缺点。
Vue.component('my-component', {
template: '<div class="my-component">{{ message }}</div>',
data() {
return {
message: 'Hello'
}
}
})
优点:简单直接 缺点:复杂模板难以维护,没有语法高亮
Vue.component('my-component', {
template: `
<div class="my-component">
<h2>{{ title }}</h2>
<p>{{ content }}</p>
<button @click="handleClick">点击</button>
</div>
`,
data() {
return {
title: '标题',
content: '内容'
}
},
methods: {
handleClick() {
console.log('clicked')
}
}
})
优点:支持多行,可读性好 缺点:仍然没有语法高亮和智能提示
<script type="text/x-template" id="my-component-template">
<div class="my-component">
<h2>{{ title }}</h2>
<p>{{ content }}</p>
</div>
</script>
<script>
Vue.component('my-component', {
template: '#my-component-template',
data() {
return {
title: '标题',
content: '内容'
}
}
})
</script>
优点:模板分离,支持语法高亮 缺点:模板和组件定义分离,不利于维护
<template>
<div class="my-component">
<h2>{{ title }}</h2>
<p>{{ content }}</p>
</div>
</template>
<script>
export default {
data() {
return {
title: '标题',
content: '内容'
}
}
}
</script>
<style scoped>
.my-component {
padding: 20px;
}
</style>
优点:模板、脚本、样式封装在一起,最佳实践 缺点:需要构建工具支持
组件支持大部分 Vue 实例的选项:
Vue.component('my-component', {
// 模板
template: '...',
// 数据
data() {
return { ... }
},
// 接收外部数据
props: {
title: String,
count: {
type: Number,
default: 0
}
},
// 计算属性
computed: {
doubleCount() {
return this.count * 2
}
},
// 方法
methods: {
doSomething() { ... }
},
// 监听器
watch: {
count(newVal, oldVal) {
console.log('count changed')
}
},
// 生命周期钩子
created() { ... },
mounted() { ... },
destroyed() { ... },
// 局部指令
directives: { ... },
// 局部过滤器
filters: { ... },
// 局部组件
components: { ... }
})
组件不支持 el 选项,因为组件通过标签使用,不需要挂载点:
// ❌ 组件不支持 el
Vue.component('my-component', {
el: '#app' // 无效
})
<div id="app">
<counter></counter>
<counter></counter>
<counter></counter>
<p>总点击次数:{{ total }}</p>
</div>
<script>
Vue.component('counter', {
template: `
<div>
<button @click="increment">+1</button>
<span>{{ count }}</span>
<button @click="decrement">-1</button>
</div>
`,
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
this.$emit('change', this.count)
},
decrement() {
this.count--
this.$emit('change', this.count)
}
}
})
new Vue({
el: '#app',
data: {
total: 0
},
methods: {
updateTotal(count) {
// 这里需要更复杂的逻辑来计算总数
}
}
})
</script>
Vue.component('user-card', {
template: `
<div class="user-card">
<img :src="user.avatar" :alt="user.name">
<div class="info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button @click="viewProfile">查看详情</button>
</div>
</div>
`,
props: {
user: {
type: Object,
required: true
}
},
methods: {
viewProfile() {
this.$emit('view', this.user.id)
}
}
})
Vue.component('tab-item', {
template: `
<div v-show="active">
<slot></slot>
</div>
`,
props: {
title: String,
active: Boolean
}
})
Vue.component('tabs', {
template: `
<div class="tabs">
<ul class="tab-header">
<li
v-for="(tab, index) in tabs"
:key="index"
:class="{ active: index === currentIndex }"
@click="currentIndex = index"
>
{{ tab.title }}
</li>
</ul>
<div class="tab-content">
<slot></slot>
</div>
</div>
`,
data() {
return {
currentIndex: 0,
tabs: []
}
},
mounted() {
this.tabs = this.$children
}
})
模板必须有根元素
组件模板必须有一个根元素包裹:
// ❌ 错误:多个根元素
template: `
<h2>标题</h2>
<p>内容</p>
`
// ✅ 正确:单一根元素
template: `
<div>
<h2>标题</h2>
<p>内容</p>
</div>
`
组件定义顺序
建议在创建 Vue 实例之前定义所有组件:
// ✅ 正确顺序
Vue.component('component-a', { ... })
Vue.component('component-b', { ... })
new Vue({ el: '#app' })
// ❌ 错误顺序:实例创建后再定义组件
new Vue({ el: '#app' })
Vue.component('component-c', { ... }) // 可能无法使用
组件定义要点: