Vue--》打造细腻颗粒感 —— 细分组件颗粒图

写文背景:当涉及到前端开发时,我们经常会发现自己在不同的组件中复用相似的逻辑,在过去我们可能会通过混入、高阶组件或者全局组件等方式来实现代码的复用;或者一个组件逻辑太多,写出了超多的响应式ref数据和五花八门的函数,对于对接项目的人内心只想说:能别在堆屎了嘛

对于一个大型项目来说,即使是初始开发者后期也会发现,随着项目需求的迭代,一个组件都有可能会被提出n个修改需求,原型设计图变变变的,导致部分代码失效,被迫返工,但是返工之后发现,这个组件光script标签里面就已经写了五百多行了,ref和各种函数乱飞,导致即使是我这个一手开发者来说看着也是一个头两个大,反正就是:

那么到底有什么办法能够解决这个比较操蛋的事情呢?其实解决方法很简单,那就是把事件进行细分。举个例子:比如你一天中做了很多事情,其中有一件事情做错了,时隔多少天之后,这个错误引发了一个问题,虽然你知道这是什么事情但是经过多少天之后的遗忘,你发现已经忘记不知道是什么地方引发了这个事情出错,这就很费精力的去找引发这个问题的诱因。事情少还好,一旦躲起来你都可能连是什么事情导致出错的都不知道。如何解决呢?只需要将一天中的每一件事情都抽离出来单独标记,即使后面出现问题也能够快速定位找到问题所在,即使忘记问题出自哪里,针对每一个独立的事件进行一一排查也能快速找到,这就是我今天要讲的vue组件中使用hook函数,划分更细颗粒度。

闲话少说,直接上操作,从下面的项目而言,基本上大部分人都采用下面这种操作vue3组合式API的这种操作,将相关的逻辑组合在一起,减少了在不同选项之间来回切换的需要,如下:

javascript 复制代码
<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
import { useRoute } from "vue-router";
// 导入接口函数
import { reqGetCategory } from "@/api/category";
import { reqGetBannerData } from '@/api/home'
// 导入接口数据类型
import type { CategoryListResult, CategoryList } from '@/api/category/type'
import type { BannerData, bannerResult } from '@/api/home/type'

// 获取路由实例对象
const route = useRoute();
// 分类数据
let categoryData = ref<CategoryList>();
// 轮播图数据
let bannerList = ref<bannerResult[]>()

// 获取分类数据
const getCategory = async () => {
    let res: CategoryListResult = await reqGetCategory(route.params.id as string)
    categoryData.value = res.result
}
// 获取轮播图数据
const getBannerData = async () => {
    let res: BannerData = await reqGetBannerData({ distributionSite: '2' })
    bannerList.value = res.result
}

watch(() => route.params.id, ()=> {
    getCategory()
})
onMounted(() => {
    getCategory()
    getBannerData()
})
</script>

从上面这位佬(博主) 的写法来看,代码一目了然,挑不出任何毛病出来,但是这仅仅是正常的组件情况,还有非正常的组件情况,也就是我上面描述的,组件逻辑非常复杂,JS含码量极高,出来一开始所说的抽离组件的方式,有没有其它的方法就是单纯的把一个组件中的逻辑抽离出来呢?

如下,这里博主把上面两个,一个是分类的请求接口,一个是轮播图的请求接口都抽出来作为hook函数,给大家看看实际效果。从命名开始,分类 and 轮播 顾名思义将其抽出如下方式,把composables作为hook函数的文件夹,相得益彰:

对于分类接口抽出如下,直接作为一个函数,在函数内部调用该相应的接口函数并将数据赋值给响应式数据,最后将数据给return出去:

javascript 复制代码
import { ref, onMounted, watch } from "vue";
import { useRoute } from "vue-router";
// 导入接口函数
import { reqGetCategory } from "@/api/category";
// 导入接口数据类型
import type { CategoryListResult, CategoryList } from '@/api/category/type'

export function useCategory() {
  // 获取路由实例对象
  const route = useRoute();
  // 分类数据
  let categoryData = ref<CategoryList>();

  // 获取分类数据
  const getCategory = async () => {
    let res: CategoryListResult = await reqGetCategory(route.params.id as string)
    categoryData.value = res.result
  }
  onMounted(() => getCategory())
  watch(() => route.params.id, ()=> getCategory())
  // 将数据或方法return出去
  return {
    categoryData
  }
}

最后在index.vue组件中进行导入并把响应式数据解构出来,如下两行代码搞定一个逻辑函数:

javascript 复制代码
<script setup lang="ts">
import { useCategory } from "./composables/useCategory"
const { categoryData } = useCategory()
</script>

这个时候可能就会有人出来抬杠了,哎呀,你通过解构数据,会让这个数据失去响应式呀,即使拿到数据后面数据变化了有啥用,页面还是不会变的呀!能思考到解构会让数据失去响应式这一知识点我先给你点个赞,但是你忽略了一个问题,就是到底解构什么会导致失去响应式?

在v3中确实需要注意的是,对于 ref 的解构,如果直接解构 ref 内部的值,的确会丢失响应式,然而,如果只是解构包含 ref 的对象本身,响应式是不会丢失的,这是因为v3的响应式系统在处理解构赋值时会自动保持响应式,这意味着当你从一个 ref 中解构出数据时,解构出来的数据仍然保持响应式。我这么说大家可能觉得有点拗口哈,这里我直接给出代码演示,方便大家理解:

javascript 复制代码
// 解构 ref 内部值导致丢失响应式
import { ref } from 'vue';
const data = ref({ name: 'Vue' });
const { name } = data.value; // 这里 `name` 是普通的字符串,不再是响应式的
console.log(name); // 'Vue'

// 解构包含 ref 的对象不会丢失响应式
import { ref } from 'vue';
const data = ref({ name: 'Vue' });
const { value } = data; // 这里 `value` 仍然是响应式的
console.log(value.name); // 'Vue'

ok,既然我们解构出来的数据仍然是响应式的,那么这里就再出来另一个轮播图数据吧,如下:

javascript 复制代码
import { ref, onMounted } from "vue";
import { reqGetBannerData } from '@/api/home'
import type { BannerData, bannerResult } from '@/api/home/type'

export function useBanner() {
  // 轮播图数据
  let bannerList = ref<bannerResult[]>()
  // 获取轮播图数据
  const getBannerData = async () => {
    let res: BannerData = await reqGetBannerData({ distributionSite: '2' })
    bannerList.value = res.result
  }
  onMounted(() => getBannerData())
  return {
    bannerList
  }
}

最后我们仍在index.vue组件中调用hook把数据解构出来,如下:

javascript 复制代码
<script setup lang="ts">
import { useCategory } from "./composables/useCategory"
import { useBanner } from "./composables/useBanner"

const { categoryData } = useCategory()
const { bannerList } = useBanner()
</script>

卧槽,看看,这才是丝滑的组件,如果一个组件逻辑函数贼多采用这种写法无敌了感觉,这里我打印一下看看该hook函数返回给我的是不是响应式数据,验证一下:

javascript 复制代码
<script setup lang="ts">
import { useCategory } from "./composables/useCategory"
import { useBanner } from "./composables/useBanner"

const { categoryData } = useCategory()
const { bannerList } = useBanner()

console.log(categoryData.value)

</script>

控制台给我打印这个结果,我靠,啥意思?undefined?难道我没拿到数据嘛,数据不是响应式?如果产生怀疑说明你又陷入一个误区了,当你直接在组件中console.log的时候,你设置的异步函数中的数值categoryData还没有被赋值呢,也就是说你的console.log打印的时机是在hook函数执行完之前就打印了,得到的结果肯定undefined,没有任何毛病。

那么我如何才能在index.vue组件中打印出我想要的数据呢? 这里直接watch即可,一旦数据发生变化再打印,这也就意味着console.log打印时机一定是在hook函数执行之后才打印的,这就很完美了。

javascript 复制代码
<script setup lang="ts">
import { watch } from "vue";
import { useCategory } from "./composables/useCategory"
import { useBanner } from "./composables/useBanner"

const { categoryData } = useCategory()
const { bannerList } = useBanner()

watch(categoryData, () => {
    console.log(categoryData.value)
})
</script>

控制台给出如下结果,可以看到结果被打印出来了,而且也是响应式的,完美:

将组件逻辑代码抽离出来到组合函数中通常是一个很好的做法,特别是当你需要在多个组件之间共享逻辑时。然而,要谨慎使用,避免过度抽离,保持代码的可读性和组件的上下文访问能力:

优势

1)代码复用: 组合函数可以使相似的逻辑可以在多个组件中被复用,从而避免了重复编写相同的代码。

2)可维护性: 将逻辑抽离到组合函数中可以使组件本身更加简洁、易读,并且便于维护,因为每个函数只包含一个特定的功能。

3)单一职责: 通过组合函数,可以将组件的不同功能划分得更加清晰,让每个函数专注于一个特定的功能,符合单一职责原则。

4)测试: 组合函数可以更容易地进行单元测试,因为它们是普通的 JavaScript 函数,可以在不涉及组件渲染的情况下进行测试。

劣势

1)上下文丢失: 在组合函数中,你可能无法访问到组件实例的上下文,比如 emit、refs、$parent 等,这可能会使一些功能的实现变得困难。

2)可读性问题: 如果过度抽离逻辑,可能会导致组件模板和逻辑之间的关联不够明显,降低代码的可读性。

3)学习曲线: 对于新手来说,理解和使用组合函数需要一定的学习成本,因为这是v3中的新概念。

具体选择还是看项目的复杂程度和难易吧,根据实际情况选择合适的编码风格!

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试