跳转到内容

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

刀刀

4/8/2025

0 字

0 分钟

    学习明灯
  • UP主:三十的前端课,视频地址:前往学习

需求分析

下面来看一个需求:

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

效果图

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

数据结构
js
let 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 渲染出四个模块出来先。

html
<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 判断文本和索引,渲染不同内容和样式的元素。

html
<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>
const createStyle = (title, index) => {
  console.log("执行了该方法");
  let 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;
};
const 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("执行了。");
  let 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"
        ></TextComponent>
      </div>
    </div>
  </div>
</template>

该方法只会在最初始的时候执行了八次,后续修改无关紧要的变量比如 num 时,也不会额外触发该方法。性能上更佳。

在代码层面,还可以使用策略模式进一步简化代码。

jsx
function TextComponent({ title, index, goods }) {
  console.log("执行了。");
  let styleObj = {};
  let text = "";
  let emnu = {
    热销排行: function () {
      if (index === 0) {
        styleObj.background = "red";
        styleObj.color = "#fff";
        text = "Top1";
      } else {
        styleObj.background = "orange";
        styleObj.color = "#fff";
        text = "Top2";
      }
    },
    利润排行: function () {
      styleObj.background = "pink";
      styleObj.color = "#000";
      text = goods.profit + "%";
    },
    热门主题: function () {
      styleObj.display = "none";
    },
    低价好物: function () {
      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 要求使用者了解相关内容,可读性差
  • 源数据改写在数据比较复杂的情况下改写会麻烦,若该数据还需要传回给后端还需要取出额外内容

总体效果