{T}

组件化开发

渲染评级分数

对于简单的评级需求,就可以使用组件。这样,只需要一行代码就可以实现评级需求。比如下面的代码,rate 是 1 到 5 的整数,通过 slice 方法,我们直接渲染出对应数量的星星即可

Vue SFC
"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate)

src/components/Rate.vue

Vue SFC
<template>
  <div :style="fontstyle">
    {{rate}}
  </div>
</template>

<script setup>
  import { defineProps,computed, } from 'vue';
  let props = defineProps({
    value: Number,
    theme:{type:String,default:'orange'}
  })
  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>
html
<template>
  <Rate :value="score"></Rate>
  <Rate :value="3" ></Rate>
  <Rate :value="4" theme="red"></Rate>
  <Rate :value="1" theme="green"></Rate>
</template>

<script setup>
  import {ref} from 'vue'
  import Rate from '../components/rate.vue'

  let score = ref(3)
</script>

组件事件

在 Vue 中,使用 emit 来对外传递事件,这样父元素就可以监听 Rate 组件内部的变化。对 Rate 组件进行改造,首先星星都是普通的文本,没有办法单独绑定 click 事件。所以我们要对模板进行改造,每个星星都用 span 包裹,并且我们可以用 width 属性控制宽度,支持小数的评分显示

html
<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 {computed, defineProps, ref} from "vue";
  let props = defineProps({
    value: Number,
    theme: {type: String, default: 'orange'}
  })
  let width = ref(props.value)

  function mouseOver(i) {
    width.value = i
  }

  function mouseOut() {
    width.value = props.value
  }

  const fontwidth = computed(() => `width:${width.value}em;`)

  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>

<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>

因为现在是通过宽度显示星星,所以我们还可以支持 3.5 分的小数评级,并且支持鼠标滑过的时候选择不同的评分

Vue SFC
<Rate :value="3.5" ></Rate>

img

在点击五角星选择评分的时候,把当前评分传递给父组件即可。使用 defineEmit 来定义对外“发射”的数据,在点击评分的时候触发即可

Vue SFC
<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 @click="onRate(num)" @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
      </span>
    </div>
  </div>
</template>

<script setup>
  import {computed, defineEmits, defineProps, ref} from "vue";

  let props = defineProps({
    value: Number,
    theme: {type: String, default: 'orange'}
  })
  let width = ref(props.value)

  function mouseOver(i) {
    width.value = i
  }

  function mouseOut() {
    width.value = props.value
  }

  let emits = defineEmits('update-rate')

  function onRate(num){
    emits('update-rate',num)
  }

  const fontwidth = computed(() => `width:${width.value}em;`)

  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>

<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>

在下面的代码中,我们使用 @update-rate 接收 Rate 组件 emit 的数据,并且修改 score 的值,这样就完成了数据修改后的更新。

Vue SFC
<template>
  <Rate :value="score"></Rate>
  <Rate :value="3" ></Rate>
  <Rate :value="4" theme="red"></Rate>
  <Rate :value="1" theme="green"></Rate>
  <h3>事件</h3>
  <rateEvent :value="score" @update-rate="update"></rateEvent>

</template>

<script setup>
  import {ref} from 'vue'
  import Rate from '../components/rate.vue'
  import rateEvent from '../components/rateEvent.vue'

  let score = ref(3)
  function update(num){
    score.value = num
  }
</script>

组件的 v-model

对于自定义组件来说,v-model 是传递属性和接收组件事件两个写法的简写。

把属性名修改成 modelValue,然后如果我们想在前端页面进行点击评级的操作,只需要通过 update:modelValue 这个 emit 事件发出通知

javascript
let props = defineProps({
  modelValue: Number,
  theme:{type:String,default:'orange'}
})
let emits = defineEmits(['update:modelValue'])

完整代码

Vue SFC
<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 @click="onRate(num)" @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
      </span>
    </div>
  </div>
</template>

<script setup>
  import {computed, defineEmits, defineProps, ref} from "vue";

  let props = defineProps({
    modelValue: Number,
    theme: {type: String, default: 'orange'}
  })
  let emits = defineEmits(['update:modelValue'])
  let width = ref(props.modelValue)

  function onRate(num){
    emits('update:modelValue',num)
  }

  function mouseOver(i) {
    width.value = i
  }

  function mouseOut() {
    width.value = props.modelValue
  }

  const fontwidth = computed(() => `width:${width.value}em;`)

  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>

<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>
html
<template>
  <h1>你的评分是 {{score}}</h1>
  <Rate v-model="score"></Rate>
</template>

插槽

在 Vue 中直接使用 slot 组件来显示组件的子元素,也就是所谓的插槽

Vue SFC
<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>

使用 Rate 组件的时候,组件的子元素都会放在评级组件之前。除了文本,也可以传递其他组件或者 html 标签

Vue SFC
<Rate v-model="score">课程评分</Rate>
<Rate v-model="score">
  <img width="14" src="/favicon.ico">
</Rate>