原文地址Mastering Vue 3 Composables: A Comprehensive Style Guide
原文作者是一名来自德国的全栈开发者,有媒体信息学的背景。
Vue 3 的发布迎来了一次变革,从 Options API 转向 Composition API。这一转变的核心在于“可组合性”的概念——利用 Vue 响应式功能的模块化功能。这一变化为框架注入了更大的灵活性和代码可重用性。然而,它也带来了挑战,特别是跨项目的可组合性实现不一致,这通常会导致复杂且难以维护的代码库。
本风格指南旨在协调可组合项的编码实践,重点是生成干净、可维护和可测试的代码。尽管可组合项可能看起来是一种新事物,但它们本质上只是函数。因此,本指南的建议基于经过时间考验的良好软件设计原则。
无论您是刚刚接触 Vue 3 还是一位致力于标准化团队编码风格的经验丰富的开发人员,本指南都可以作为综合资源。
文件命名
使用use前缀并遵循帕斯卡命名法
useCounter.ts
useApiRequest.ts
counter.ts
APIrequest.ts
Composable 命名
使用描述性名称
export function useUserData() {}
export function useData() {}
文件夹结构
将可组合项组织到composables文件夹中
src/
|-- composables/
|-- useCounter.ts
|-- useUser.ts
|-- useApiRequest.ts
参数传递
对四个或更多参数使用对象参数
useUserData({ id: 1, fetchOnMount: true, token: 'abc', locale: 'en' })
useUserData(1, true, 'session')
useUserData(1, true, 'session', 'en')
错误处理
暴露错误状态
function useExampleGoodError() {
const error = ref(null)
try {
// Do something
}
catch (e) {
error.value = e
}
return { error }
}
function useExampleBadError() {
try {
// Do something
}
catch (e) {
console.error('An error occurred', e)
}
}
避免混合UI和业务逻辑
在可组合项中将 UI 与业务逻辑解耦
可组合项应专注于管理状态和业务逻辑,避免特定于 UI 的行为,例如 toast 或警报。将 UI 逻辑与业务逻辑分开将确保您的可组合项可重用和可测试。
export function useUserData(userId) {
const user = ref(null)
const error = ref(null)
const fetchUser = async () => {
try {
const response = await axios.get(`/users/${userId}`)
user.value = response.data
}
catch (e) {
error.value = e
}
}
return { user, error, fetchUser }
}
<script lang="ts" setup>
const {user,error,fetchUser} = useUserData(1)
watch(error, newValue => {
if (newValue) {
showToast('An error occurred')
}
})
</script>
export function useUserData(userId) {
const user = ref(null)
const fetchUser = async () => {
try {
const response = await axios.get(`/users/${userId}`)
user.value = response.data
}
catch (e) {
showToast('An error occurred')
}
}
return { user, fetchUser }
}
可组合项的剖析
良好地构建可组合项
遵循明确定义的结构的可组合项更容易理解、使用和维护。理想情况下,它应该由以下组件组成:
- 主要状态: 可组合项管理的主要只读状态。
- 支持状态:附加只读状态,保存API请求或错误状态等的值。
- 方法:负责更新主要和支持状态的功能。他们可以调用API、管理cookie,甚至调用其他可组合项。
通过确保您的可组合项遵循这种解剖结构,您可以使开发者更轻松地使用它们,从而提高整个项目的代码质量。
export function useUserData(userId) {
// 主要状态
const user = ref(null)
// 支持状态
const status = ref('idle')
const error = ref(null)
// 方法
const fetchUser = async () => {
try {
const response = await axios.get(`/users/${userId}`)
user.value = response.data
status.value = 'success'
}
catch (e) {
status.value = 'error'
error.value = e
}
}
return { user, status, error, fetchUser }
}
export function useUserData(userId) {
// 主要状态不清晰
const user = ref(null)
const count = ref(0)
const message = ref('初始化...')
// 主要方法,函数有多重副作用
const fetchUserAndIncrement = async () => {
message.value = '获取用户数据并且统计加1...'
try {
const response = await axios.get(`/users/${userId}`)
user.value = response.data
message.value = '获取用户数据成功'
}
catch (e) {
message.value = '获取用户数据失败'
}
count.value++ // 在这里统计加1
}
// 方法:完全不同类型的任务
const setMessage = (newMessage) => {
message.value = newMessage
}
return { user, count, message, fetchUserAndIncrement, setMessage }
}
功能核心,命令式外壳
(可选) 使用功能核心命令式外壳模式
构建可组合项,使核心逻辑具有功能性且没有副作用,而命令式外壳则处理Vue特定个的或副作用操作。遵循这一原则可以使您的可组合项更易于测试、调试和维护。
// 功能核心
const calculate = (a, b) => a + b
// 命令式外壳
export function useCalculator() {
const result = ref(0)
const add = (a, b) => {
result.value = calculate(a, b)
}
return { result, add }
}
// 混合了主要功能和副作用函数
export function useCalculator() {
const result = ref(0)
const add = (a, b) => {
// 核心逻辑中的副作用
console.log('Adding ' + a + ' and ' + b)
result.value = a + b
}
return { result, add }
}
单一职责原则
对可组合项使用SRP
可组合项应遵循单一职责原则,这意味着它应该只有一个更改理由。换句话说,可组合项应该只有一项工作或职责。违反此原则可能会导致可组合项难以理解、维护和测试。
export function useCounter() {
const count = ref(0)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
return { count, increment, decrement }
}
export function useUserAndCounter(userId) {
const user = ref(null)
const count = ref(0)
const fetchUser = async () => {
try {
const response = await axios.get(`/users/${userId}`)
user.value = response.data
}
catch (e) {
console.error('An error occurred while fetching the user', e)
}
}
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
return { user, count, fetchUser, increment, decrement }
}
可组合项的文件结构
可组合项功能的一致排序
虽然可以调整精确的顺序以满足您的项目或团队的需求,但至关重要的是,所选顺序在整个代码库中保持一致。
以下是文件结构的建议:
- 初始化:用于设置初始化逻辑的代码
- Refs:响应式数据
- Computed:计算属性
- Methods:方法
- Lifecycle:生命周期钩子
- Watchers:观察者
这只是一个示例的顺序,您可以根据自己的需求进行调整。但要确保在整个代码库中保持一致。
import { computed, onMounted, ref } from 'vue'
export function useCounter() {
// Refs
const count = ref(0)
// Computed
const doubleCount = computed(() => count.value * 2)
// Methods
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
// Lifecycle
onMounted(() => {
console.log('Component mounted')
})
return { count, doubleCount, increment, decrement }
}
结论
本文提出的指南旨在提供编写干净、可测试且高效的Vue3可组合项的最佳实践。虽然这些建议源于既定的软件设计原则和实践经验的组合,但它们绝不是详尽的或普遍适用的。
编程往往更像一门艺术而不是一门科学。随着您在Vue之旅中不断成长,您可能发现更适合您的特定用例的不同策略和模式。关键是要保持一致、可扩展且可维护的代码库。因此,请随意根据您的项目的需要调整这些指南。
我愿意接受更多的想法、改进和现实世界中的例子。如果您有任何适合您的建议或不同方法,请随时在下面的评论中分享。我们可以共同发展这些指南,使其成为对Vue社区更有用的资源。
评论区
评论加载中...