组织代码抽取组合式函数

当在编写 Vue 项目的时候,很容易把一个组件写得非常大,比如写出下面这样的代码。
<script setup>// 鼠标跟踪器示例import { ref, onMounted, onUnmounted } from 'vue'const x = ref(0)const y = ref(0)function update(event) {x.value = event.pageXy.value = event.pageY}onMounted(() => window.addEventListener('mousemove', update))onUnmounted(() => window.removeEventListener('mousemove', update))// 异步数据请求示例const data = ref(null)const error = ref(null)fetch('...').then((res) => res.json()).then((json) => (data.value = json)).catch((err) => (error.value = err))</script><template>Mouse position is at: {{ x }}, {{ y }}<div v-if="error">Oops! Error encountered: {{ error.message }}</div><div v-else-if="data">Data loaded:<pre>{{ data }}</pre></div><div v-else>Loading...</div></template><style>.greeting {color: red;font-weight: bold;}</style>
当代码逻辑变得复杂,就很容易写出一座 shi 山,项目维护变得困难,那我们应该如果关注分离点呢?
为更好的代码组织抽取组合式函数
抽取组合式函数不仅是为了复用,也是为了代码组织。随着组件复杂度的增高,你可能会最终发现组件多得难以查询和理解。组合式 API 会给予你足够的灵活性,让你可以基于逻辑问题将组件代码拆分成更小的函数:
<script setup>import { useFeatureA } from './featureA.js'import { useFeatureB } from './featureB.js'import { useFeatureC } from './featureC.js'const { foo, bar } = useFeatureA()const { baz } = useFeatureB(foo)const { qux } = useFeatureC(baz)</script>
在某种程度上,你可以将这些提取出的组合式函数看作是可以相互通信的组件范围内的服务。
什么是“组合式函数”?
在 Vue 应用的概念中,“组合式函数”是一个利用 Vue 组合式 API 来封装和复用有状态逻辑的函数。
当构建前端应用时,我们常常需要复用公共任务的逻辑。例如为了在不同地方格式化时间而抽取一个可复用的函数。这个格式化函数封装了无状态的逻辑:它在接收一些输入后立刻返回所期望的输出。复用无状态逻辑的库有很多,诸如你可能听到过的 lodash。
相比之下,有状态逻辑负责管理会随时间而变化的状态。一个简单的例子是跟踪当前鼠标在页面中的位置。
然后重新改造上面例子的代码逻辑。
鼠标跟踪器示例,我们创建 mouse 文件。
// mouse.jsimport { ref, onMounted, onUnmounted } from 'vue'// 按照惯例,组合式函数名以“use”开头export function useMouse() {// 被组合式函数封装和管理的状态const x = ref(0)const y = ref(0)// 组合式函数可以随时更改其状态。function update(event) {x.value = event.pageXy.value = event.pageY}// 一个组合式函数也可以挂靠在所属组件的生命周期上// 来启动和卸载副作用onMounted(() => window.addEventListener('mousemove', update))onUnmounted(() => window.removeEventListener('mousemove', update))// 通过返回值暴露所管理的状态return { x, y }}
异步数据请求示例,我们创建 fetch 文件。
// fetch.jsimport { ref } from 'vue'export function useFetch(url) {const data = ref(null)const error = ref(null)fetch(url).then((res) => res.json()).then((json) => (data.value = json)).catch((err) => (error.value = err))return { data, error }}
现在我们在组件里只需要导入使用:
<script setup>import { useFetch } from './fetch.js'import { useMouse } from './mouse.js'// 鼠标跟踪器示例const { x, y } = useMouse()// 异步数据请求示例const { data, error } = useFetch('...')</script><template>Mouse position is at: {{ x }}, {{ y }}<div v-if="error">Oops! Error encountered: {{ error.message }}</div><div v-else-if="data">Data loaded:<pre>{{ data }}</pre></div><div v-else>Loading...</div></template><style>.greeting {color: red;font-weight: bold;}</style>
现在我们的组建就变得非常的简洁干净,一眼看上去就知道我们的页面有什么逻辑。看到 useFetch 和 useMouse 这样的逻辑,
如你所见,核心逻辑一点都没有被改变,我们做的只是把它移到一个外部函数中去,并返回需要暴露的状态。和在组件中一样,你也可以在组合式函数中使用所有的组合式 API 函数。现在,在任何组件中都可以使用 useMouse() 功能了。
然而更酷的一点是,你还可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数。这使得我们可以像使用多个组件组合成整个应用一样,用多个较小且逻辑独立的单元来组合形成复杂的逻辑。
如果你有 React 的开发经验,你可能注意到组合式函数和自定义 React hook 非常相似。组合式 API 的一部分灵感正来自于 React hook,Vue 的组合式函数也的确在逻辑组合能力上与 React hook 相近。然而,Vue 的组合式函数是基于 Vue 细粒度的响应性系统,这和 React hook 的执行模型有本质上的不同。
