[{"data":1,"prerenderedAt":380},["ShallowReactive",2],{"content:\u002Fposts\u002F2025\u002Fvue3-composables-guide":3,"surround:\u002Fposts\u002F2025\u002Fvue3-composables-guide":368},{"id":4,"title":5,"body":6,"canonical":19,"categories":341,"date":343,"description":34,"draft":344,"extension":345,"image":346,"meta":347,"navigation":351,"path":352,"permalink":353,"readingTime":354,"recommend":359,"references":353,"seo":360,"sitemap":361,"stem":362,"tags":363,"type":353,"updated":366,"__hash__":367},"content\u002Fposts\u002F2025\u002Fvue3-composables-guide.md","【翻译】Vue3 组合式API 完全指南",{"type":7,"value":8,"toc":306},"minimark",[9,23,26,29,32,35,39,43,55,62,66,69,75,81,84,87,95,98,101,107,114,120,123,126,132,138,142,146,149,155,164,170,173,176,179,192,195,201,207,211,215,218,224,230,233,237,240,246,252,255,258,261,264,285,288,294,297,300,303],[10,11,12],"auths-alex",{},[13,14,15,16],"p",{},"原文地址",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Falexop.dev\u002Fposts\u002Fmastering-vue-3-composables-a-comprehensive-style-guide\u002F",[21],"nofollow","Mastering Vue 3 Composables: A Comprehensive Style Guide",[24,25],"hr",{},[13,27,28],{},"Vue 3 的发布迎来了一次变革，从 Options API 转向 Composition API。这一转变的核心在于“可组合性”的概念——利用 Vue 响应式功能的模块化功能。这一变化为框架注入了更大的灵活性和代码可重用性。然而，它也带来了挑战，特别是跨项目的可组合性实现不一致，这通常会导致复杂且难以维护的代码库。",[13,30,31],{},"本风格指南旨在协调可组合项的编码实践，重点是生成干净、可维护和可测试的代码。尽管可组合项可能看起来是一种新事物，但它们本质上只是函数。因此，本指南的建议基于经过时间考验的良好软件设计原则。",[13,33,34],{},"无论您是刚刚接触 Vue 3 还是一位致力于标准化团队编码风格的经验丰富的开发人员，本指南都可以作为综合资源。",[36,37,38],"h2",{"id":38},"文件命名",[40,41,42],"h3",{"id":42},"使用use前缀并遵循帕斯卡命名法",[44,45,52],"pre",{"className":46,"code":48,"filename":49,"language":50,"meta":51},[47],"language-ts","useCounter.ts\nuseApiRequest.ts\n","Good","ts","",[53,54,48],"code",{"__ignoreMap":51},[44,56,60],{"className":57,"code":58,"filename":59,"language":50,"meta":51},[47],"counter.ts\nAPIrequest.ts\n","Bad",[53,61,58],{"__ignoreMap":51},[36,63,65],{"id":64},"composable-命名","Composable 命名",[40,67,68],{"id":68},"使用描述性名称",[44,70,73],{"className":71,"code":72,"filename":49,"language":50,"meta":51},[47],"export function useUserData() {}\n",[53,74,72],{"__ignoreMap":51},[44,76,79],{"className":77,"code":78,"filename":59,"language":50,"meta":51},[47],"export function useData() {}\n",[53,80,78],{"__ignoreMap":51},[36,82,83],{"id":83},"文件夹结构",[40,85,86],{"id":86},"将可组合项组织到composables文件夹中",[44,88,93],{"className":89,"code":91,"filename":49,"language":92,"meta":51},[90],"language-text","src\u002F\n|-- composables\u002F\n  |-- useCounter.ts\n  |-- useUser.ts\n  |-- useApiRequest.ts\n","text",[53,94,91],{"__ignoreMap":51},[36,96,97],{"id":97},"参数传递",[40,99,100],{"id":100},"对四个或更多参数使用对象参数",[44,102,105],{"className":103,"code":104,"filename":49,"language":50,"meta":51},[47],"useUserData({ id: 1, fetchOnMount: true, token: 'abc', locale: 'en' })\n",[53,106,104],{"__ignoreMap":51},[44,108,112],{"className":109,"code":110,"filename":111,"language":50,"meta":51},[47],"useUserData(1, true, 'session')\n","Also Good: 参数比较少时",[53,113,110],{"__ignoreMap":51},[44,115,118],{"className":116,"code":117,"filename":59,"language":50,"meta":51},[47],"useUserData(1, true, 'session', 'en')\n",[53,119,117],{"__ignoreMap":51},[36,121,122],{"id":122},"错误处理",[40,124,125],{"id":125},"暴露错误状态",[44,127,130],{"className":128,"code":129,"filename":49,"language":50,"meta":51},[47],"function useExampleGoodError() {\n    const error = ref(null)\n    try {\n        \u002F\u002F Do something\n    }\n    catch (e) {\n        error.value = e\n    }\n    return { error }\n}\n",[53,131,129],{"__ignoreMap":51},[44,133,136],{"className":134,"code":135,"filename":59,"language":50,"meta":51},[47],"function useExampleBadError() {\n    try {\n        \u002F\u002F Do something\n    }\n    catch (e) {\n        console.error('An error occurred', e)\n    }\n}\n",[53,137,135],{"__ignoreMap":51},[36,139,141],{"id":140},"避免混合ui和业务逻辑","避免混合UI和业务逻辑",[40,143,145],{"id":144},"在可组合项中将-ui-与业务逻辑解耦","在可组合项中将 UI 与业务逻辑解耦",[13,147,148],{},"可组合项应专注于管理状态和业务逻辑，避免特定于 UI 的行为，例如 toast 或警报。将 UI 逻辑与业务逻辑分开将确保您的可组合项可重用和可测试。",[44,150,153],{"className":151,"code":152,"filename":49,"language":50,"meta":51},[47],"export function useUserData(userId) {\n    const user = ref(null)\n    const error = ref(null)\n    const fetchUser = async () => {\n        try {\n            const response = await axios.get(`\u002Fusers\u002F${userId}`)\n            user.value = response.data\n        }\n        catch (e) {\n            error.value = e\n        }\n    }\n\n    return { user, error, fetchUser }\n}\n",[53,154,152],{"__ignoreMap":51},[44,156,162],{"className":157,"code":159,"filename":160,"language":161,"meta":51},[158],"language-html","\u003Cscript lang=\"ts\" setup>\n  const {user,error,fetchUser} = useUserData(1)\n  watch(error, newValue => {\n    if (newValue) {\n      showToast('An error occurred')\n    }\n  })\n\u003C\u002Fscript>\n","在组件里使用","html",[53,163,159],{"__ignoreMap":51},[44,165,168],{"className":166,"code":167,"filename":59,"language":50,"meta":51},[47],"export function useUserData(userId) {\n    const user = ref(null)\n    const fetchUser = async () => {\n        try {\n            const response = await axios.get(`\u002Fusers\u002F${userId}`)\n            user.value = response.data\n        }\n        catch (e) {\n            showToast('An error occurred')\n        }\n    }\n\n    return { user, fetchUser }\n}\n",[53,169,167],{"__ignoreMap":51},[36,171,172],{"id":172},"可组合项的剖析",[36,174,175],{"id":175},"良好地构建可组合项",[13,177,178],{},"遵循明确定义的结构的可组合项更容易理解、使用和维护。理想情况下，它应该由以下组件组成：",[180,181,182,186,189],"ul",{},[183,184,185],"li",{},"主要状态： 可组合项管理的主要只读状态。",[183,187,188],{},"支持状态：附加只读状态，保存API请求或错误状态等的值。",[183,190,191],{},"方法：负责更新主要和支持状态的功能。他们可以调用API、管理cookie，甚至调用其他可组合项。",[13,193,194],{},"通过确保您的可组合项遵循这种解剖结构，您可以使开发者更轻松地使用它们，从而提高整个项目的代码质量。",[44,196,199],{"className":197,"code":198,"filename":49,"language":50,"meta":51},[47],"export function useUserData(userId) {\n    \u002F\u002F 主要状态\n    const user = ref(null)\n\n    \u002F\u002F 支持状态\n    const status = ref('idle')\n    const error = ref(null)\n\n    \u002F\u002F 方法\n    const fetchUser = async () => {\n        try {\n            const response = await axios.get(`\u002Fusers\u002F${userId}`)\n            user.value = response.data\n            status.value = 'success'\n        }\n        catch (e) {\n            status.value = 'error'\n            error.value = e\n        }\n    }\n\n    return { user, status, error, fetchUser }\n}\n",[53,200,198],{"__ignoreMap":51},[44,202,205],{"className":203,"code":204,"filename":59,"language":50,"meta":51},[47],"export function useUserData(userId) {\n    \u002F\u002F 主要状态不清晰\n    const user = ref(null)\n    const count = ref(0)\n    const message = ref('初始化...')\n\n    \u002F\u002F 主要方法，函数有多重副作用\n    const fetchUserAndIncrement = async () => {\n        message.value = '获取用户数据并且统计加1...'\n        try {\n            const response = await axios.get(`\u002Fusers\u002F${userId}`)\n            user.value = response.data\n            message.value = '获取用户数据成功'\n        }\n        catch (e) {\n            message.value = '获取用户数据失败'\n        }\n        count.value++ \u002F\u002F 在这里统计加1\n    }\n\n    \u002F\u002F 方法：完全不同类型的任务\n    const setMessage = (newMessage) => {\n        message.value = newMessage\n    }\n\n    return { user, count, message, fetchUserAndIncrement, setMessage }\n}\n",[53,206,204],{"__ignoreMap":51},[36,208,210],{"id":209},"功能核心命令式外壳","功能核心，命令式外壳",[40,212,214],{"id":213},"可选-使用功能核心命令式外壳模式","(可选) 使用功能核心命令式外壳模式",[13,216,217],{},"构建可组合项，使核心逻辑具有功能性且没有副作用，而命令式外壳则处理Vue特定个的或副作用操作。遵循这一原则可以使您的可组合项更易于测试、调试和维护。",[44,219,222],{"className":220,"code":221,"filename":49,"language":50,"meta":51},[47],"\u002F\u002F 功能核心\nconst calculate = (a, b) => a + b\n\n\u002F\u002F 命令式外壳\nexport function useCalculator() {\n    const result = ref(0)\n\n    const add = (a, b) => {\n        result.value = calculate(a, b)\n    }\n\n    return { result, add }\n}\n",[53,223,221],{"__ignoreMap":51},[44,225,228],{"className":226,"code":227,"filename":59,"language":50,"meta":51},[47],"\u002F\u002F 混合了主要功能和副作用函数\nexport function useCalculator() {\n    const result = ref(0)\n\n    const add = (a, b) => {\n        \u002F\u002F 核心逻辑中的副作用\n        console.log('Adding ' + a + ' and ' + b)\n\n        result.value = a + b\n    }\n\n    return { result, add }\n}\n",[53,229,227],{"__ignoreMap":51},[36,231,232],{"id":232},"单一职责原则",[40,234,236],{"id":235},"对可组合项使用srp","对可组合项使用SRP",[13,238,239],{},"可组合项应遵循单一职责原则，这意味着它应该只有一个更改理由。换句话说，可组合项应该只有一项工作或职责。违反此原则可能会导致可组合项难以理解、维护和测试。",[44,241,244],{"className":242,"code":243,"filename":49,"language":50,"meta":51},[47],"export function useCounter() {\n    const count = ref(0)\n\n    const increment = () => {\n        count.value++\n    }\n\n    const decrement = () => {\n        count.value--\n    }\n\n    return { count, increment, decrement }\n}\n",[53,245,243],{"__ignoreMap":51},[44,247,250],{"className":248,"code":249,"filename":59,"language":50,"meta":51},[47],"export function useUserAndCounter(userId) {\n    const user = ref(null)\n    const count = ref(0)\n\n    const fetchUser = async () => {\n        try {\n            const response = await axios.get(`\u002Fusers\u002F${userId}`)\n            user.value = response.data\n        }\n        catch (e) {\n            console.error('An error occurred while fetching the user', e)\n        }\n    }\n\n    const increment = () => {\n        count.value++\n    }\n\n    const decrement = () => {\n        count.value--\n    }\n\n    return { user, count, fetchUser, increment, decrement }\n}\n",[53,251,249],{"__ignoreMap":51},[36,253,254],{"id":254},"可组合项的文件结构",[40,256,257],{"id":257},"可组合项功能的一致排序",[13,259,260],{},"虽然可以调整精确的顺序以满足您的项目或团队的需求，但至关重要的是，所选顺序在整个代码库中保持一致。",[13,262,263],{},"以下是文件结构的建议：",[265,266,267,270,273,276,279,282],"ol",{},[183,268,269],{},"初始化：用于设置初始化逻辑的代码",[183,271,272],{},"Refs：响应式数据",[183,274,275],{},"Computed：计算属性",[183,277,278],{},"Methods：方法",[183,280,281],{},"Lifecycle：生命周期钩子",[183,283,284],{},"Watchers：观察者",[13,286,287],{},"这只是一个示例的顺序，您可以根据自己的需求进行调整。但要确保在整个代码库中保持一致。",[44,289,292],{"className":290,"code":291,"language":50,"meta":51},[47],"import { computed, onMounted, ref } from 'vue'\n\nexport function useCounter() {\n    \u002F\u002F Refs\n    const count = ref(0)\n\n    \u002F\u002F Computed\n    const doubleCount = computed(() => count.value * 2)\n\n    \u002F\u002F Methods\n    const increment = () => {\n        count.value++\n    }\n\n    const decrement = () => {\n        count.value--\n    }\n\n    \u002F\u002F Lifecycle\n    onMounted(() => {\n        console.log('Component mounted')\n    })\n\n    return { count, doubleCount, increment, decrement }\n}\n",[53,293,291],{"__ignoreMap":51},[36,295,296],{"id":296},"结论",[13,298,299],{},"本文提出的指南旨在提供编写干净、可测试且高效的Vue3可组合项的最佳实践。虽然这些建议源于既定的软件设计原则和实践经验的组合，但它们绝不是详尽的或普遍适用的。",[13,301,302],{},"编程往往更像一门艺术而不是一门科学。随着您在Vue之旅中不断成长，您可能发现更适合您的特定用例的不同策略和模式。关键是要保持一致、可扩展且可维护的代码库。因此，请随意根据您的项目的需要调整这些指南。",[13,304,305],{},"我愿意接受更多的想法、改进和现实世界中的例子。如果您有任何适合您的建议或不同方法，请随时在下面的评论中分享。我们可以共同发展这些指南，使其成为对Vue社区更有用的资源。",{"title":51,"searchDepth":307,"depth":307,"links":308},4,[309,314,317,320,323,326,329,330,331,334,337,340],{"id":38,"depth":310,"text":38,"children":311},2,[312],{"id":42,"depth":313,"text":42},3,{"id":64,"depth":310,"text":65,"children":315},[316],{"id":68,"depth":313,"text":68},{"id":83,"depth":310,"text":83,"children":318},[319],{"id":86,"depth":313,"text":86},{"id":97,"depth":310,"text":97,"children":321},[322],{"id":100,"depth":313,"text":100},{"id":122,"depth":310,"text":122,"children":324},[325],{"id":125,"depth":313,"text":125},{"id":140,"depth":310,"text":141,"children":327},[328],{"id":144,"depth":313,"text":145},{"id":172,"depth":310,"text":172},{"id":175,"depth":310,"text":175},{"id":209,"depth":310,"text":210,"children":332},[333],{"id":213,"depth":313,"text":214},{"id":232,"depth":310,"text":232,"children":335},[336],{"id":235,"depth":313,"text":236},{"id":254,"depth":310,"text":254,"children":338},[339],{"id":257,"depth":313,"text":257},{"id":296,"depth":310,"text":296},[342],"译","2025-01-06 12:00:00",false,"md","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002Fvue3-composables-guide.jpg",{"aside":348},[349,350],"toc","auth-alex",true,"\u002Fposts\u002F2025\u002Fvue3-composables-guide",null,{"text":355,"minutes":356,"time":357,"words":358},"10 min read",9.08,544800,1816,1,{"title":5,"description":34},{"loc":352},"posts\u002F2025\u002Fvue3-composables-guide",[364,365],"Vue","组合式API","2026-02-07 23:23:50","L_r1uvzeHUnjHb6xQSttuA3FojSyJAouhJkJxL9_LZI",[369,374],{"title":370,"path":371,"stem":372,"date":373,"type":353,"children":-1},"【翻译】 如何在 Vue 中使用Props变体","\u002Fposts\u002F2025\u002Fhow-to-use-the-variant-props-pattern-in-vue","posts\u002F2025\u002FHow-to-Use-the-Variant-Props-Pattern-in-Vue","2025-01-06 11:24:05",{"title":375,"path":376,"stem":377,"date":378,"type":379,"children":-1},"本站为什么要从 Hexo 迁移到 Nuxt","\u002Fposts\u002F2025\u002Fwhy-migrate-to-nuxt","posts\u002F2025\u002Fwhy-migrate-to-nuxt","2025-01-06 16:38:36","story",1789642585756]