重点学习:vue3.0之组件通信机制defineProps(组件接收外部传来的参数)、defineEmits(向组件外部传递参数)。
1. 评级组件第一版
简单的评级需求,只需要一行代码就可以实现:
"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate)
只需要传入评分值 rate,就可以渲染出不同数量的星星。
src/components 目录,新建 Rate.vue,然后写出下面的代码:
<!-- 这是个评分组件 -->
<template>
<div>
{{ rate }}
</div>
</template>
<script setup>
import { defineProps, computed } from 'vue';
let props = defineProps({ // defineProps定义接受外部传来的参数
value: Number // 外部传来的value参数只能是数值
})
let rate = computed(() => "★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))
</script>
使用Rate组件,通过:value 的方式,通过属性把 value 传递给 Rate 组件。
<template>
<h1>这是首页</h1>
<Counter />
<Todos />
<Rate :value="score"></Rate>
</template>
<script setup>
import {ref} from "vue"
import Counter from '../components/Counter.vue';
import Todos from '../components/Todos.vue';
import Rate from '../components/Rate.vue';
let score = ref(3);
</script>
2. 评级组件第二版
在组件中内置一些主题颜色,加入 CSS 的内容
<!-- 这是个评分组件,可以defineProps接收外部传进来的参数
组件使用方式:
<Rate :value=4 theme="red"></Rate>
参数value表示几个星,theme表示星的颜色。
-->
<template>
<div :style="fontstyle">
{{ rate }}
</div>
</template>
<script setup>
import { defineProps, computed } from 'vue';
let props = defineProps({ // defineProps定义接受外部传来的参数
value: Number, // 外部传来的value参数只能是数值
theme: { type: String, default: "orange" } // 外部传进来theme只能是字符串
})
let rate = computed(() => "★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))
const themeObj = { 'black': '#00', 'white': '#fff', 'red': '#f5222d', 'orange': '#fa541c', 'yellow': '#fadb14', 'green': '#73d13d', 'blue': '#40a9ff', }
const fontstyle = computed(()=>{
return `color:${themeObj[[props.theme]]}`
})
</script>
在…pages/home.vue中,使用Rate组件,通过:value 的方式,通过属性把 value 传递给 Rate 组件,
<template>
<h1>这是首页</h1>
<Counter />
<Todos />
<Rate :value="score"></Rate>
<!-- theme前不需加冒号 -->
<Rate :value=4 theme="green"></Rate>
<Rate :value=1 theme="yellow"></Rate>
</template>
<script setup>
import {ref} from "vue"
import Counter from '../components/Counter.vue';
import Todos from '../components/Todos.vue';
import Rate from '../components/Rate.vue';
let score = ref(3);
</script>
效果如下:
3. 组件事件
让组件的星星可点击,并且让点击后的评分值能够传递到父组件。
使用 defineEmits 来对外传递事件,这样父元素就可以监听 Rate 组件内部的变化。
1. 让组件的星星可点击
前面组件中,星星都是普通的文本,没有办法单独绑定 click 事件。要想让星星可以点击,需要每个星星都用 span 包裹,并且我用 width 属性控制宽度,支持小数的评分显示。
<!-- 这是个评分组件 -->
<!-- @绑定用户交互
mouseout:当鼠标移出某元素,移入和移出其子元素时触发。鼠标在元素内移动,只要鼠标不断在其子元素间划来划去就会不断触发。
mouseover:当鼠标移入某元素,移入和移出其子元素时触发。鼠标在元素内移动,只要鼠标不断在其子元素间划来划去就会不断触发。
-->
<template>
<div :style="fontstyle">
<div class='rate' @mouseout="mouseOut">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">☆</span>
<span class='hollow' :style="fontwidth">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
</span>
</div>
</div>
</template>
<script setup>
import { defineProps, computed, ref } from 'vue';
let props = defineProps({ // defineProps定义接受外部传来的参数
value: Number, // 外部传来的value参数只能是数值
theme: { type: String, default: "orange" } // 外部传进来theme只能是字符串
})
let rate = computed(() => "★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))
const themeObj = { 'black': '#00', 'white': '#fff', 'red': '#f5222d', 'orange': '#fa541c', 'yellow': '#fadb14', 'green': '#73d13d', 'blue': '#40a9ff', }
const fontstyle = computed(() => {
return `color:${themeObj[[props.theme]]}`
})
// 评分宽度
let width = ref(props.value)
// 鼠标移入span,修改评分宽度
function mouseOver(i) {
width.value = i;
}
// 鼠标离开span,展示评分宽度
function mouseOut() {
width.value = props.value;
}
// 展示宽度
const fontwidth = computed(() => `width:${width.value}em;`)
</script>
<style scoped>
.rate {
position: relative;
display: inline-block;
}
.rate>span.hollow {
position: absolute;
display: inline-block;
top: 0;
left: 0;
width: 0;
overflow: hidden;
}</style>
在pages/about.vue中的template中添加如下代码<RateV2 :value="3.5" />
:
<template>
<h1>这是关于页面</h1>
<button @click="loadding">更换图标</button>
<button @click="reset">重置图标</button>
<RateV2 :value="3.5" />
</template>
http://localhost:5173/#/about 页面,鼠标滑动效果:
2. 使用 defineEmits 来对外传递事件
首先,子组件RateV2.vue中的变化主要有定义发射给父组件的事件,并将此事件emits给父组件:
// ①:子组件定义发射给父组件的方法 "update-rate"
let emits = defineEmits('update-rate');
// ②:子组件要触发的onRate方法中,调用emits并传入发射给父组件的方法'update-rate'以及参数num
function onRate(num) {
emits('update-rate', num)
}
并为实心的星星绑定一个click事件。@click="onRate(num)"
其次,父组件about.vue的变化:
绑定子组件的@update-rate
事件,收到事件后执行父组件的update
方法。update
方法中,接受子组件的num数值,并更新父组件的score变量。
<template>
<h1>这是关于页面</h1>
<button @click="loadding">更换图标</button>
<button @click="reset">重置图标</button>
你的评分是 {{ score }}
<RateV2 :value="3.5" @update-rate="update"/>
</template>
<script setup>
import { ref } from 'vue'
import RateV2 from '../components/RateV2.vue';
import useFavicon from '../utils/favicon';
let { favicon, reset } = useFavicon();
function loadding() {
favicon.value = "/geek-favicon-32x32.webp"
}
// 父组件定义一个变量
let score = ref(3.5)
// 子组件的num赋值给父组件的变量
function update(num) { score.value = num }
</script>
4. 组件的 v-model
v-model 可以在组件上使用以实现双向绑定。
v-model 是传递属性和接收组件事件两个写法的简写。
默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。
let props = defineProps({ // defineProps定义接受外部传来的参数
modelValue: Number, // 必须是这个变量名 modelValue
theme: { type: String, default: "orange" } // 外部传进来theme只能是字符串
})
省略代码。。。。
// ①子组件,定义发射给父组件的方法
// let emits = defineEmits('update-rate');
let emits = defineEmits(['update:modelValue'])
// ②子组件要触发的onRate方法中,调用emits并传入发射给父组件的方法'update:modelValue'以及参数num
function onRate(num) {
emits('update:modelValue', num)
}
在父组件中,v-model绑定这样使用:
<template>
<h1>这是关于页面</h1>
<button @click="loadding">更换图标</button>
<button @click="reset">重置图标</button>
你的评分是 {{ score }}
<!-- <RateV2 :value="3.5" @update-rate="update"/> -->
<RateV2 v-model="score"></RateV2>
</template>
效果同## 3. 组件事件
章节。
5. 插槽
在 Vue 中直接使用 slot 组件来显示组件的子元素,也就是所谓的插槽。
通过<slot></slot>
在组件中创建插槽。插槽中,除了文本,也可以传递其他组件或者 html 标签 。
<template>
<div :style="fontstyle">
<!-- 这里创建一个插槽,用来放子元素 ,这里是放在了评分组件之前 -->
<slot></slot>
<div class='rate' @mouseout="mouseOut">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">☆</span>
<span class='hollow' :style="fontwidth">
<span @click="onRate(num)" @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
</span>
</div>
</div>
</template>
父组件中使用方式如下,这里是放了一个html标签元素。
<template>
<RateV2 v-model="score">
<img width="14" src="/vite.svg">
</RateV2>
</template>
Rate 组件前面显示一个图片。
6. 使用useVModel封装组件
这是一个给经常封装组件的小伙伴的大好利器。
在components/中新建Test.vue,代码如下:
<template>
<div>
<!-- v-model同步数据 -->
name:
<input v-model="_name"/>
age:
<input v-model="_age"/>
sex:
<input v-model="_sex"/>
</div>
</template>
<script lang="ts" setup>
import { useVModel } from '@vueuse/core'
// 通过defineProps 来定义接收 父组件的参数
const props = defineProps({
name: String,
age: String,
sex: String
})
// 向父组件发射事件update:开头
const emit = defineEmits(['update:name', 'update:age', 'update:sex'])
// useVModel返回的数据就是响应式数据
const _name = useVModel(props, 'name', emit)
const _age = useVModel(props, 'age', emit)
const _sex = useVModel(props, 'sex', emit)
</script>
接着,在usevue.vue中使用它:
<template>
<Test
v-model:name="formData.name"
v-model:age="formData.age"
v-model:sex="formData.sex"
></Test>
{{ formData }}
</div>
</template>
<script setup>
import Test from '../components/Test.vue'
import {reactive} from 'vue';
const formData = reactive({
name: 'lily',
age: '8',
sex: 'boy'
})
</script>