从一个需求出发如何更优雅写代码
作者:Yuan Tang
更新于:5 个月前
字数统计:1.4k 字
阅读时长:5 分钟
需求分析
下面来看一个需求:
- 四个商品推荐栏目,问题在于,四个栏目基本样式都一样,但是文字部分不一样。
- 颜色和排行类型,写死固定,比如热销排行,固定第一个红色,第二个橘色,利润排行固定粉色,低价好物固定灰色。
- 数据一起返回。用循环展示四个商品栏目,数据结构里只有栏目名字,栏目商品的售价和利润率。

接着看一下后端的数据返回的数据,是通过一个接口返回一个数组对象,都存放着标题、商品价格、商品利润和商品地址。
js
const data = [
{
title: '热销排行',
goodsData: [
{
price: '100.0',
profit: 30,
src: ''
},
{
price: '78.0',
profit: 20,
src: ''
},
]
},
{
title: '利润排行',
goodsData: [
{
price: '30.0',
profit: 130,
src: ''
},
{
price: '333.0',
profit: 55,
src: ''
},
]
},
{
title: '热门主题',
goodsData: [
{
price: '137.0',
src: ''
},
{
price: '78.0',
src: ''
},
]
},
{
title: '低价好物',
goodsData: [
{
price: '100.0',
profit: 30,
src: ''
},
{
price: '78.0',
profit: 20,
src: ''
},
]
}
]
思路分析
首先想到的是通过 v-for
渲染出四个模块出来先。
vue
<div v-for="(item, index) in data" :key="index">
<div>{{ item.title }}
</div>
<div>
<div v-for="(goods, i) in item.goodsData" :key="i">
<el-image src="" />
</div>
</div>
</div>
渲染出来效果如下所示:

接下来要分析的是栏目该如何处理渲染,它分几种情况:是否渲染、渲染的颜色、渲染的文本等。
关于处理方案,此处有几个不同的思路:
- 四个栏目写成四种文本,通过
v-if
判断显示哪个 - 文本部分不同的样式文字通过函数返回
- 利用
jsx
根据不同栏目渲染不同的样式 - 修改源数据
实现
v-if
首先先来看第一种通过 v-if
判断文本和索引,渲染不同内容和样式的元素。
vue
<div v-for="(item, index) in data" :key="index">
<div>{{ item.title }}
</div>
<div>
<div v-for="(goods, i) in item.goodsData" :key="i">
<el-image src="" />
<div v-if="item.title ==='热销排行' && i === 0" style="background: red; color: #fff;">Top1
</div>
<div v-if="item.title ==='热销排行' && i === 1" style="background: orange; color: #fff;">
Top2
</div>
<div v-if="item.title ==='利润排行'" style="background: pink; color: #000;">
{{ goods.profit }}%
</div>
<div v-if="item.title ==='低价好物'" style="background: #555; color: #000;">
{{ goods.price }}
</div>
</div>
</div>
</div>
该方式简单粗暴,缺陷是 HTML 代码量多且冗余,后续不易维护,因此不是很推荐使用。
函数返回
既然 div
盒子内容一致,那么可以通过一个函数统一返回,减少代码量。
vue
<script setup>
function createStyle(title, index) {
console.log('执行了该方法')
const styleObj = {}
switch (title) {
case '热销排行':
styleObj.color = '#fff'
if (index === 0)
styleObj.background = 'red'
else styleObj.background = 'orange'
break
case '利润排行':
styleObj.color = '#000'
styleObj.background = 'pink'
break
case '低价好物':
styleObj.color = '#000'
styleObj.background = 'gray'
break
case '热门主题':
styleObj.display = 'none'
break
default:
break
}
return styleObj
}
function createText(title, index, good) {
console.log('执行了该方法')
let txt = ''
switch (title) {
case '热销排行':
if (index === 0)
txt = 'Top1'
else txt = 'Top2'
break
case '利润排行':
txt = `${good.profit}%`
break
case '低价好物':
txt = good.price
break
default:
break
}
return txt
}
</script>
<template>
<div @click="() => { num += 1 }">
点击事件触发,createStyle与createText函数依旧触发:{{ num }}
</div>
<div v-for="(item, index) in data" :key="index">
<div>{{ item.title }}</div>
<div>
<div v-for="(goods, i) in item.goodsData" :key="i">
<el-image src="" />
<div :style="createStyle(item.title, i)">
{{ createText(item.title, i, goods) }}
</div>
</div>
</div>
</div>
</template>
虽然 template
内的代码量减少了,但是函数被执行了多次,且在操作 num
的点击事件时,因为该 vue
文件的渲染依赖于变量 num
,在 num
发生变化时,整个组件都会触发更新,因此函数方法都会重新执行,造成一定的性能压力。
JSX
vue
<script setup lang="jsx">
function TextComponent({ title, index, goods }) {
console.log('执行了。')
const styleObj = {}
let text = ''
switch (title) {
case '热销排行':
if (index === 0) {
styleObj.background = 'red'
styleObj.color = '#fff'
text = 'Top1'
}
else {
styleObj.background = 'orange'
styleObj.color = '#fff'
text = 'Top2'
}
break
case '利润排行':
styleObj.background = 'pink'
styleObj.color = '#000'
text = `${goods.profit}%`
break
case '热门主题':
styleObj.display = 'none'
break
case '低价好物':
styleObj.background = 'gray'
styleObj.color = '#000'
text = goods.price
break
default:
break
}
return <div style={styleObj}>{text}</div>
}
</script>
<template>
<div @click="() => { num += 1 }">
{{ num }}
</div>
<div v-for="(item, index) in data" :key="index">
<div>{{ item.title }}</div>
<div>
<div v-for="(goods, i) in item.goodsData" :key="i">
<el-image src="" />
<TextComponent :title="item.title" :index="i" :goods="goods" />
</div>
</div>
</div>
</template>
该方法只会在最初始的时候执行了八次,后续修改无关紧要的变量比如 num
时,也不会额外触发该方法。性能上更佳。
在代码层面,还可以使用策略模式进一步简化代码。
jsx
function TextComponent({ title, index, goods }) {
console.log('执行了。')
const styleObj = {}
let text = ''
const emnu = {
热销排行() {
if (index === 0) {
styleObj.background = 'red'
styleObj.color = '#fff'
text = 'Top1'
}
else {
styleObj.background = 'orange'
styleObj.color = '#fff'
text = 'Top2'
}
},
利润排行() {
styleObj.background = 'pink'
styleObj.color = '#000'
text = `${goods.profit}%`
},
热门主题() {
styleObj.display = 'none'
},
低价好物() {
styleObj.background = 'gray'
styleObj.color = '#000'
text = goods.price
},
}
obj[title]()
return <div style={styleObj}>{text}</div>
}
修改源数据
通过修改源数据,统一字段,渲染的时侯就可以直接渲染无需判断了。
vue
<script setup>
data.forEach((item, index) => {
switch (item.title) {
case '热销排行':
item.goodsData.forEach((goods, i) => {
goods.styleObj = {
background: 'red',
color: '#fff'
}
goods.text = 'Top1'
if (i !== 0) {
goods.styleObj.background = 'orange'
goods.text = 'Top2'
}
})
break
// ...省略
default:
break
}
})
</script>
<template>
<div v-for="(item, index) in data" :key="index">
<div>{{ item.title }}</div>
<div>
<div v-for="(goods, i) in item.goodsData" :key="i">
<el-image src="" />
<div :style="goods.styleObj">
{{ goods.text }}
</div>
</div>
</div>
</div>
</template>
比较
比较上方的几个方法,都有各自的优缺点。
- 使用函数的方法在组件的数据改变引发组件的更新,等于组件重新执行渲染,造成性能浪费
- JSX 要求使用者了解相关内容,可读性差
- 源数据改写在数据比较复杂的情况下改写会麻烦,若该数据还需要传回给后端还需要取出额外内容