Vue3组件化实战:从0到1开发星级评分组件
在Vue开发中,组件化是核心思想之一。通过组件化,我们可以像“搭积木”一样拆分、复用代码,让复杂页面的开发变得高效且可维护。
本文将以一个星级评分组件(Rate) 为例,从基础渲染到交互增强,一步步拆解Vue3组件化的设计思路和实现细节,帮你快速掌握自定义组件开发的核心要点。
一、组件化开发核心认知
在开始编码前,我们先理清组件化的核心概念:
- 组件本质:把页面中可复用的功能模块(模板、逻辑、样式)封装成独立的.vue文件,实现“一次编写,多处复用”。
- 组件分类:
- 通用型组件:按钮、表单、弹窗等通用功能(如Element3组件库);
- 业务型组件:和具体业务绑定的组件(如购物车、登录弹窗)。
评分组件属于通用型组件,核心需求是:支持1-5星的分数渲染、鼠标悬停预览、点击修改评分、自定义主题色,以及灵活的扩展能力。
二、基础实现:渲染星级评分
2.1 核心思路
评分组件的基础是根据传入的分数,渲染对应数量的实心星星。这里有一个极简的实现技巧:利用字符串的slice方法截取星星字符,一行代码就能实现分数对应渲染。
核心代码:
"★★★★★☆☆☆☆☆".slice(5 – rate, 10 – rate)
- 当rate=3时,截取结果为★★★☆☆;
- 当rate=4.5时(后续扩展小数支持),可通过宽度控制半星显示。
2.2 基础组件编写
在src/components目录下新建Rate.vue,实现基础的分数渲染:
<template>
<div>
{{ rate }}
</div>
</template>
<script setup>
import { defineProps, computed } from 'vue';
// 定义接收的属性:分数值(数字类型)
const props = defineProps({
value: Number
});
// 计算要显示的星星字符串
const rate = computed(() =>
"★★★★★☆☆☆☆☆".slice(5 – props.value, 10 – props.value)
);
</script>
2.3 组件使用
在父组件中引入并使用Rate组件,传递分数值:
<template>
<Rate :value="score"></Rate>
</template>
<script setup>
import { ref } from 'vue';
import Rate from './components/Rate.vue';
// 初始评分为3
const score = ref(3);
</script>
此时页面会显示★★★☆☆,基础的评分渲染就完成了。
三、进阶优化:主题定制+小数评分
3.1 自定义主题色
为组件添加theme属性,支持自定义星星颜色(默认橙色):
<template>
<div :style="fontStyle">
{{ rate }}
</div>
</template>
<script setup>
import { defineProps, computed } from 'vue';
const props = defineProps({
value: Number,
theme: {
type: String,
default: 'orange' // 默认主题色
}
});
// 主题色映射表
const themeMap = {
black: '#000',
white: '#fff',
red: '#f5222d',
orange: '#fa541c',
yellow: '#fadb14',
green: '#73d13d',
blue: '#40a9ff'
};
const rate = computed(() =>
"★★★★★☆☆☆☆☆".slice(5 – props.value, 10 – props.value)
);
// 计算字体样式(颜色)
const fontStyle = computed(() => ({
color: themeMap[props.theme] || themeMap.orange
}));
</script>
使用时指定主题色:
<Rate :value="3" theme="red"></Rate>
<Rate :value="4" theme="green"></Rate>
3.2 支持小数评分(鼠标悬停预览)
基础版只能渲染整数评分,我们通过“双层星星+宽度控制”实现小数/悬停预览效果:
<template>
<div :style="fontStyle">
<div class="rate" @mouseout="handleMouseOut">
<!– 底层空心星 –>
<span @mouseover="handleMouseOver(num)" v-for="num in 5" :key="num">☆</span>
<!– 上层实心星(通过宽度控制显示数量) –>
<span class="hollow" :style="fontWidth">
<span @mouseover="handleMouseOver(num)" v-for="num in 5" :key="num">★</span>
</span>
</div>
</div>
</template>
<script setup>
import { defineProps, computed, ref } from 'vue';
const props = defineProps({
value: Number,
theme: { type: String, default: 'orange' }
});
// 响应式宽度(控制实心星显示数量)
const width = ref(props.value);
// 鼠标悬停:预览对应分数
const handleMouseOver = (num) => {
width.value = num;
};
// 鼠标移出:恢复原始分数
const handleMouseOut = () => {
width.value = props.value;
};
// 主题色/宽度计算
const themeMap = { /* 同上 */ };
const fontStyle = computed(() => ({ color: themeMap[props.theme] || themeMap.orange }));
const fontWidth = computed(() => ({ width: `${width.value}em` }));
</script>
<style scoped>
.rate {
position: relative;
display: inline-block;
}
.rate > .hollow {
position: absolute;
top: 0;
left: 0;
width: 0;
overflow: hidden;
}
</style>
此时传入value=3.5,就能显示3.5星;鼠标悬停到第4星时,会预览4星效果。
四、交互增强:点击修改评分(emit通信)
组件需要把“用户点击的评分”传递给父组件,Vue3中通过defineEmits实现组件事件派发:
4.1 子组件派发事件
修改Rate.vue,添加点击事件并派发评分:
<template>
<div :style="fontStyle">
<div class="rate" @mouseout="handleMouseOut">
<span @mouseover="handleMouseOver(num)" v-for="num in 5" :key="num">☆</span>
<span class="hollow" :style="fontWidth">
<!– 新增点击事件 –>
<span
@click="handleRate(num)"
@mouseover="handleMouseOver(num)"
v-for="num in 5"
:key="num"
>★</span>
</span>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits, computed, ref } from 'vue';
// 定义要派发的事件
const emit = defineEmits(['update-rate']);
// 点击评分:派发事件给父组件
const handleRate = (num) => {
emit('update-rate', num);
};
// 其余代码(props/theme/width等)不变
</script>
4.2 父组件接收事件
父组件监听update-rate事件,更新评分值:
<template>
<h1>你的评分:{{ score }}</h1>
<Rate :value="score" @update-rate="updateScore"></Rate>
</template>
<script setup>
import { ref } from 'vue';
import Rate from './components/Rate.vue';
const score = ref(3.5);
// 接收子组件的评分并更新
const updateScore = (num) => {
score.value = num;
};
</script>
五、简化用法:组件的v-model
Vue3支持自定义组件使用v-model,本质是props + emit的语法糖,无需手动写@update-rate,让代码更简洁。
5.1 子组件适配v-model
Vue3中自定义组件的v-model默认绑定modelValue属性,事件默认是update:modelValue:
<script setup>
// 1. 修改props名称为modelValue
const props = defineProps({
modelValue: Number, // 替换原来的value
theme: { type: String, default: 'orange' }
});
// 2. 修改事件名为update:modelValue
const emit = defineEmits(['update:modelValue']);
// 3. 点击事件派发update:modelValue
const handleRate = (num) => {
emit('update:modelValue', num);
};
// 4. 宽度初始值改为props.modelValue
const width = ref(props.modelValue);
const handleMouseOut = () => {
width.value = props.modelValue;
};
// 其余代码不变
</script>
5.2 父组件简化使用
直接用v-model绑定评分值,无需手动监听事件:
<template>
<h1>你的评分:{{ score }}</h1>
<!– 简化为v-model –>
<Rate v-model="score"></Rate>
</template>
六、灵活扩展:插槽(Slot)
像原生<button>可以嵌套内容一样,我们给Rate组件添加插槽,支持自定义前置内容(如文本、图标):
6.1 子组件添加插槽
修改Rate.vue的模板,加入<slot>:
<template>
<div :style="fontStyle">
<!– 插槽:自定义前置内容 –>
<slot></slot>
<div class="rate" @mouseout="handleMouseOut">
<!– 星星部分代码不变 –>
</div>
</div>
</template>
6.2 父组件使用插槽
可以传递文本、图片等任意内容:
<template>
<!– 插槽传递文本 –>
<Rate v-model="score">课程评分:</Rate>
<!– 插槽传递图片 –>
<Rate v-model="score">
<img width="14" src="/favicon.ico" alt="评分">
</Rate>
</template>
七、总结
本文从0到1实现了一个星级评分组件,核心知识点总结:
八、扩展思考(进阶方向)
这个基础版评分组件还可以进一步优化:
如果需要更完善的实现,可以参考Element3的Rate组件源码:Element3 评级组件。
组件化的核心是“复用”和“解耦”,掌握了评分组件的开发思路,你可以举一反三,开发出表单、弹窗、表格等更多通用组件。希望本文能帮你理解Vue3组件化的精髓,让你的前端开发更高效!






