在一次开发中,leader review我代码的时候,你这代码怎么写的那个乱,vue3你给写成vue2,状态和函数分离,完全不组合式api,还有,你是一点都不做组件拆分啊,一个文件上w行代码。 我找项目给你ddl延1d,1d后重新给我review。
我,优化上w行的组件?这又不是我一个人堆的💩。
但莫得办法,拆了我难受,不拆我leader难受,我leader难受,我的工资难受😣。
一、技术选型
选择一个好的思路是代码优化至关重要的一步,方向对了,路再弯都能走到终点;方向错了,只能从一个💩山到另外一个💩山。 公司的技术栈是vue。经过3分钟思考,我收集到以下信息:
- 暴力硬拆:把相关性强的代码和视图拆成一个组件,使用props和emit、provide和inject的形式进行通信
- pinia:使用公共的状态库,将状态和视图分离,对视图按职责分离。
- ref写在组合式函数外面: 视图和状态解绑定。
- 发布订阅:dddd
但我很快就发现这俩个的问题
- 暴力硬拆:页面逻辑过于复杂,总体分为3部分存在
erDiagram
Center }|..|{ Right : uses
Center }|..|{ Left : uses
Left }|..|{ Right : uses
各种嵌套,无法很好进行拆分,处理处理很快就会发现,所有的状态又回到父组件,from巨石文件to巨石文件,死循环了属于是。 ps: 因为状态需要全体组件共享,所以大量的状态需要从父组件provide。
-
pinia: 太重了,上w行组件的状态交错复杂,能拆出5、6个store,但这些store又是单单这个页面的组件用到,增加后面维护的心智负担。
-
ref写在组合式函数外面: 还可以,虽然只能写在script setup里,这不是问题。但一言难尽,感觉有坑 ps:希望大佬能讲讲原理
const currentSession = ref<number>(1);
export const useSession = () => {
return {
currentSession
}
}
- 发布订阅:dddd,很简单,但不符合响应式编程设计。
但很快,我就从vueuse看到了曙光。effectScope
二、effectScope介绍
官方描述:
创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。对于该 API 的使用细节,请查阅对应的 RFC。
Creates an effect scope object which can capture the reactive effects (i.e. computed and watchers) created within it so that these effects can be disposed together. For detailed use cases of this API, please consult its corresponding RFC.
简单来说,就是创建一个与组件无关的函数式组件运行环境。
这个可玩性就很高了,单例,于组件无关,ioc?如果把状态看成一个一个无视图的单例component,我管你怎么嵌套,没一个state都是单例,不管在哪里用,在哪里改都是全体生效的。开干!
三、示例代码
那个上w行的组件的代码,我分割成了3,整体结构如下
简化后的逻辑有:
- 左边的变化能被中间和右边监听到。
- 中间的变化能被左右两边监听到。
简单来说就是:虽然是三个组件,但他们要像一个组件一样方便调用。
主要代码
先来一个工具函数,创建一个函数式组件运行环境
useContext
import { effectScope } from 'vue';
import type { EffectScope } from 'vue';
/**
* Any function
*/
// biome-ignore lint/suspicious/noExplicitAny: 函数参数any
// biome-ignore lint/suspicious/noConfusingVoidType: 函数返回值需要any | void
type AnyFn<T extends Array<any> = any[], R = void | any> = (...args: T) => R;
export function useContext<Fn extends AnyFn, T extends object>(
stateFactory: Fn,
): [Fn, EffectScope] {
let initialized = false;
let state: T;
const scope = effectScope(true);
const use: Fn = ((...args) => {
if (!initialized) {
state = scope.run(() => stateFactory(...args));
initialized = true;
}
return state;
}) as Fn;
return [use, scope];
}
next,使用
hooks.ts
import { useContext } from '@/hooks'
import type { Session } from "./types";
export const [useSession] = useContext(() => {
const currentSession = ref<Partial<Session>>({});
return {
currentSession,
};
});
left.vue
<script setup lang='ts'>
import { findSessionByName } from '@/api'
import { useRequest } from '@/hooks'
import { useSession } from './hooks';
const { currentSession } = useSession();
const { data, reload } = useRequest(findSessionByName, {
initialValue: {
data: {
list: []
},
code: 0,
msg: '',
}
});
onMounted(() => {
reload(undefined);
})
</script>
<template>
<div class="flex flex-col gap-2 p-4 overflow-auto scrollbar">
<div
class="flex justify-center items-center p-2 border border-solid border-yellow-200 cursor-pointer hover:text-yellow-400"
:class="{
'text-white bg-yellow-600': currentSession === session
}"
@click="currentSession.id = session.id"
v-for="session in data.data.list"
>
{{ session.topic }}
</div>
</div>
</template>
<style scoped lang='less'></style>
right.vue
<script setup lang='ts'>
import { useSession } from './hooks'
const { currentSession } = useSession();
watch(currentSession.value, (newValue) => {
console.log('currentSession right', newValue)
})
</script>
<template>
<span class="flex justify-center p-4">当前sesssion: {{ currentSession }}</span>
</template>
index.vue
<script setup lang='ts'>
import Left from './left.vue'
import { useSession } from './hooks'
import Right from './right.vue';
const { currentSession } = useSession();
</script>
<template>
<div class="flex gap-2 w-full h-full text-white">
<Left />
<span class="flex justify-center flex-1 w-0 p-4">
当前sesssion: {{ currentSession }}
</span>
<Right />
</div>
</template>
这样拆分,完美,不管哪里需要需要用到currentSession,都能直接使用useSession,简单的实现了pinia的状态,但又不如pinia的那么重,直接结构出来就是的响应式数据。不止是在这个ai项目,在后续的后台开发中,useContext也得到了大家的好评,特别是在表单、表格的拆分,体验很不错,视图分割,状态不分割。
最后,希望看完这篇文章能让大家在复杂逻辑拆分、大组件拆分得到灵感!