在 Vue 3 中,组合式 API 的出现给我们带来了很多新的编程方式和思路。
其中,Hook 是一种非常强大的工具,可以帮助我们更好地复用代码,提升开发效率。
在这篇文章中,我将详细介绍什么是 Hook,以及如何在 Vue 3 中封装一个超级好用的 Hook。
我们将通过多个实例,逐步深入探讨 Hook 的封装技巧和使用场景。
什么是 Hook
Hook 是一种将逻辑复用的方式,可以帮助我们将组件中的逻辑提取出来,形成独立的函数。
在 Vue 3 中,Hook 通常是指使用 setup
函数中的组合式 API 封装的逻辑函数。
通过 Hook,我们可以在多个组件中复用相同的逻辑,而不需要重复编写代码。
在 Vue 中使用 Hook
在 Vue 3 中,使用 Hook 非常简单。
我们可以通过在 setup
函数中定义一个函数,然后在需要的地方调用这个函数来实现逻辑复用。
下面是一个简单的示例:
<!-- src/components/ExampleComponent.vue --> <template> <div class="example"> <h1>{{ message }}</h1> </div> </template> <script setup> import { ref }from'vue' functionuseExample(){ const message =ref('Hello, Vue 3 Hook!') return{ message } } const{ message }=useExample() </script> <style scoped> .example { font-family: Avenir, Helvetica, Arial, sans-serif; text-align: center; margin-top: 60px; } </style>
封装表格 Hook
我们经常需要在多个地方使用表格组件,并且每个表格都有类似的逻辑,比如数据获取、分页、排序等。
为了避免代码重复,我们可以封装一个表格 Hook 来复用这些逻辑。
如何封装
首先,我们创建一个名为 useTable.js
的文件:
// src/hooks/useTable.js import{ ref }from'vue' import axios from'axios' exportfunctionuseTable(url){ const data =ref([]) const loading =ref(false) const currentPage =ref(1) const total =ref(0) constfetchData=async()=>{ loading.value=true try{ const response =await axios.get(url,{ params:{ page: currentPage.value, }, }) data.value= response.data.items total.value= response.data.total }catch(error){ console.error('Error fetching data:', error) }finally{ loading.value=false } } fetchData() return{ data, loading, currentPage, total, fetchData } }
然后,我们在组件中使用这个 Hook:
<!-- src/components/TableComponent.vue --> <template> <div class="table-component"> <table v-if="!loading"> <thead> <tr> <th>ID</th> <th>Name</th> </tr> </thead> <tbody> <tr v-for="item in data" :key="item.id"> <td>{{ item.id }}</td> <td>{{ item.name }}</td> </tr> </tbody> </table> <p v-else>Loading...</p> <button @click="prevPage" :disabled="currentPage === 1">Previous</button> <button @click="nextPage" :disabled="currentPage === total">Next</button> </div> </template> <script setup> import { ref }from'vue' import{ useTable }from'../hooks/useTable' const url ='https://api.example.com/data' const{ data, loading, currentPage, total, fetchData }=useTable(url) constprevPage=()=>{ if(currentPage.value>1){ currentPage.value-- fetchData() } } constnextPage=()=>{ if(currentPage.value< total.value){ currentPage.value++ fetchData() } } </script> <style scoped> .table-component { font-family: Avenir, Helvetica, Arial, sans-serif; text-align: center; margin-top: 60px; } </style>
封装 Hook 时,返回值的设计非常重要。
我们应该返回足够的信息和方法,使得使用 Hook 的组件能够完全控制和使用 Hook 的功能。
在上面的例子中,我们返回了数据、加载状态、当前页码、总页数和一个获取数据的方法。
添加分页查询
有时候,我们需要在表格中支持分页查询。
我们可以在 Hook 中加入分页查询的逻辑,并通过传参控制分页查询的行为。
如何封装
我们在 useTable.js
文件中修改 useTable
函数,加入分页查询的支持:
// src/hooks/useTable.js import{ ref }from'vue' import axios from'axios' exportfunctionuseTable(url, initialParams = {}){ const data =ref([]) const loading =ref(false) const currentPage =ref(1) const total =ref(0) const params =ref(initialParams) constfetchData=async()=>{ loading.value=true try{ const response =await axios.get(url,{ params:{ page: currentPage.value, ...params.value, }, }) data.value= response.data.items total.value= response.data.total }catch(error){ console.error('Error fetching data:', error) }finally{ loading.value=false } } fetchData() return{ data, loading, currentPage, total, params, fetchData } }
在组件中,我们可以传递初始参数,并在查询时修改参数:
<!-- src/components/TableComponent.vue --> <template> <div class="table-component"> <input v-model="query" placeholder="Search..." /> <button @click="search">Search</button> <table v-if="!loading"> <thead> <tr> <th>ID</th> <th>Name</th> </tr> </thead> <tbody> <tr v-for="item in data" :key="item.id"> <td>{{ item.id }}</td> <td>{{ item.name }}</td> </tr> </tbody> </table> <p v-else>Loading...</p> <button @click="prevPage" :disabled="currentPage === 1">Previous</button> <button @click="nextPage" :disabled="currentPage === total">Next</button> </div> </template> <script setup> import { ref }from'vue' import{ useTable }from'../hooks/useTable' const url ='https://api.example.com/data' const{ data, loading, currentPage, total, params, fetchData }=useTable(url) const query =ref('') constsearch=()=>{ params.value.query= query.value fetchData() } constprevPage=()=>{ if(currentPage.value>1){ currentPage.value-- fetchData() } } constnextPage=()=>{ if(currentPage.value< total.value){ currentPage.value++ fetchData() } } </script> <style scoped> .table-component { font-family: Avenir, Helvetica, Arial, sans-serif; text-align: center; margin-top: 60px; } </style>
支持不同接口字段
有时候,不同的 API 返回的数据格式可能不同。
我们需要在 Hook 中处理这些不同的字段,以便能够兼容不同的接口。
如何封装
我们在 useTable.js
文件中修改 useTable
函数,加入字段映射的支持:
// src/hooks/useTable.js import{ ref }from'vue' import axios from'axios' exportfunctionuseTable(url, fieldMap = {}, initialParams = {}){ const data =ref([]) const loading =ref(false) const currentPage =ref(1) const total =ref(0) const params =ref(initialParams) constfetchData=async()=>{ loading.value=true try{ const response =await axios.get(url,{ params:{ page: currentPage.value, ...params.value, }, }) data.value= response.data[fieldMap.items||'items'] total.value= response.data[fieldMap.total||'total'] }catch(error){ console.error('Error fetching data:', error) }finally{ loading.value=false } } fetchData() return{ data, loading, currentPage, total, params, fetchData } }
在组件中,我们可以传递字段映射:
<!-- src/components/TableComponent.vue --> <template> <div class="table-component"> <input v-model="query" placeholder="Search..." /> <button @click ="search">Search</button> <table v-if="!loading"> <thead> <tr> <th>ID</th> <th>Name</th> </tr> </thead> <tbody> <tr v-for="item in data" :key="item.id"> <td>{{ item.id }}</td> <td>{{ item.name }}</td> </tr> </tbody> </table> <p v-else>Loading...</p> <button @click="prevPage" :disabled="currentPage === 1">Previous</button> <button @click="nextPage" :disabled="currentPage === total">Next</button> </div> </template> <script setup> import { ref }from'vue' import{ useTable }from'../hooks/useTable' const url ='https://api.example.com/data' const fieldMap ={ items:'results', total:'total_count', } const{ data, loading, currentPage, total, params, fetchData }=useTable(url, fieldMap) const query =ref('') constsearch=()=>{ params.value.query= query.value fetchData() } constprevPage=()=>{ if(currentPage.value>1){ currentPage.value-- fetchData() } } constnextPage=()=>{ if(currentPage.value< total.value){ currentPage.value++ fetchData() } } </script> <style scoped> .table-component { font-family: Avenir, Helvetica, Arial, sans-serif; text-align: center; margin-top: 60px; } </style>
总结
以上便是我对如何在 Vue 3 项目中封装一个超级好用的 Hook 的一些心得与体会。