Skip to content

从一个需求出发如何更优雅写代码

作者:Yuan Tang
更新于:5 个月前
字数统计:1.4k 字
阅读时长:5 分钟

需求分析

下面来看一个需求:

  1. 四个商品推荐栏目,问题在于,四个栏目基本样式都一样,但是文字部分不一样。
  2. 颜色和排行类型,写死固定,比如热销排行,固定第一个红色,第二个橘色,利润排行固定粉色,低价好物固定灰色。
  3. 数据一起返回。用循环展示四个商品栏目,数据结构里只有栏目名字,栏目商品的售价和利润率。
效果图

接着看一下后端的数据返回的数据,是通过一个接口返回一个数组对象,都存放着标题、商品价格、商品利润和商品地址。

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>

渲染出来效果如下所示:

效果

接下来要分析的是栏目该如何处理渲染,它分几种情况:是否渲染、渲染的颜色、渲染的文本等。

关于处理方案,此处有几个不同的思路:

  1. 四个栏目写成四种文本,通过 v-if 判断显示哪个
  2. 文本部分不同的样式文字通过函数返回
  3. 利用 jsx 根据不同栏目渲染不同的样式
  4. 修改源数据

实现

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 要求使用者了解相关内容,可读性差
  • 源数据改写在数据比较复杂的情况下改写会麻烦,若该数据还需要传回给后端还需要取出额外内容

Contributors

Yuan Tang