如何充分利用Composition API对Vue3项目进行代码抽离(上)

简介: 本文代码略多,希望大家耐心观看

背景介绍


在2020年,Vue3的学习一直被我鸽到了11月份,在学完以后,我自己做了一个Vue3的小项目nav-url,也整理了我对于如何快速上手Vue3的几篇博客,很高兴受到了大家的指点和喜欢。


在上一篇博客中,我详细介绍了一下我发的第一版项目的特色、亮点以及所有核心功能的实现,希望大家可以前往阅读体验一下(记得用电脑打开,因为这是一个PC端的项目)


然而,这项目只是实现了一些功能,但我感觉并没有很好地利用Composition API去对代码进行整合管理。要知道,Composition API的出现就是为了解决Options API导致相同功能代码分散的现象,也有很多大佬对其做了很多的动画展示(这里我借用一下大帅搞全栈大佬精心制作的动画,他的这篇文章可以说是好评连连)


a36460306b03eaf8b820cf246c586865.jpg


7760ad93f9cb0f2e9204ddb4ff6c2a64.jpg


看了一下我项目初版的代码,简直是没有体现出Composition API的优势,可以给大家看一下某个组件内的代码


<template>
  <aside id="tabs-container">
      <div id="logo-container">
          {{ navInfos.navName }}
      </div>
      <ul id="tabs">
          <li class="tab tab-search" @click="showSearch">
              <i class="fas fa-search tab-icon"/>
              <span>快速搜索</span>
          </li>
          <li class="tab tab-save" @click="showSaveConfigAlert">
              <i class="fas fa-share-square tab-icon"></i>
              <span>保存配置</span>
          </li>
          <li class="tab tab-import" @click="showImportConfigAlert">
              <i class="fas fa-cog tab-icon"></i>
              <span>导入配置</span>
          </li>
          <br>
          <li v-for="(item, index) in navInfos.catalogue" 
              :key="index"
              class="tab"
              @click="toID(item.id)">
                <span class="li-container">
                  <i :class="['fas', `fa-${item.icon}`, 'tab-icon']" />
                  <span>{{ item.name }}</span>
                  <i class="fas fa-angle-right tab-icon tab-angle-right"/>
                </span>
          </li>
          <li class="tab add-tab" @click="addTabShow">
              <i class="fas fa-plus"/>
          </li>
      </ul>
      <!--    添加标签弹框     -->
      <tabAlert />
      <!--    保存配置弹框     -->
      <save-config @closeSaveConfigAlert="closeSaveConfigAlert" :isShow="isShowSaveAlert"/>
      <!--    导入配置弹框     -->
      <import-config @closeImportConfigAlert="closeImportConfigAlert" :isShow="isShowImportAlert"/>
  </aside>
</template>
<script>
import {ref} from 'vue'
import {useStore} from 'vuex'
import tabAlert from '../public/tabAlert/tabAlert'
import saveConfig from './childCpn/saveConfig'
import importConfig from './childCpn/importConfig'
export default {
    name: 'tabs',
    components: {
        tabAlert,
        saveConfig,
        importConfig
    },
    setup() {
        const store = useStore()     
        let navInfos = store.state    // Vuex的state对象
        let isShowSaveAlert = ref(false)           // 保存配置弹框是否展示
        let isShowImportAlert = ref(false)         // 导入配置弹框是否展示
        // 展示"添加标签弹框"
        function addTabShow() {
            store.commit('changeTabInfo', [
                {key: 'isShowAddTabAlert', value: true},
                {key: 'alertType', value: '新增标签'}
            ])
        }
        // 关闭"保存配置弹框"
        function closeSaveConfigAlert(value) {
            isShowSaveAlert.value = value
        }
        // 展示"保存配置弹框"
        function showSaveConfigAlert() {
            isShowSaveAlert.value = true
        }
        // 展示"导入配置弹框"
        function showImportConfigAlert() {
            isShowImportAlert.value = true
        }
        // 关闭"导入配置弹框"
        function closeImportConfigAlert(value) {
            isShowImportAlert.value = value
        }
        // 展示搜索框
        function showSearch() {
            if(store.state.moduleSearch.isSearch) {
                store.commit('changeIsSearch', false)
                store.commit('changeSearchWord', '')
            } else {
                store.commit('changeIsSearch', true)
            }
        }
        // 跳转到指定标签
        function toID(id) {
            const content = document.getElementById('content')
            const el = document.getElementById(`${id}`)
            let start = content.scrollTop
            let end = el.offsetTop - 80
            let each = start > end ? -1 * Math.abs(start - end) / 20 : Math.abs(start - end) / 20
            let count = 0
            let timer = setInterval(() => {
                if(count < 20) {
                    content.scrollTop += each
                    count ++
                } else {
                    clearInterval(timer)
                }
            }, 10) 
        }
        return {
            navInfos,
            addTabShow, 
            isShowSaveAlert, 
            closeSaveConfigAlert, 
            showSaveConfigAlert,
            isShowImportAlert,
            showImportConfigAlert,
            closeImportConfigAlert,
            showSearch,
            toID
        }
    }
}
</script>


上述代码是我项目中侧边栏中所有的变量以及方法,虽说变量和方法都同时存在于setup函数中了,但是仍看起来杂乱无章,若是这个组件的业务需求越来越复杂,这个setup内的代码可能更乱了


于是,我便开始构思如何抽离我的代码。后来在掘金的沸点上说了一下我的思路,并且询问了一下其他掘友的建议


52eeec5540dec7bc449498f3454251ca.png


ad95a5cb672aa8ab14b72cde5420efb6.png


7837bb78321587d136ac76550e64e780.png


其实最后一位老哥的回答对我启发很大,因此我也借鉴了一下它的思路对我的项目代码进行了抽离


准备工作


首先我得思考一个问题:抽离代码时,是按照组件单独抽离?还是按照整体功能抽离?


8e886bf139f8b3c3399aa47c738a1cdd.jpg


最后我决定按照整体的功能去抽离代码,具体功能列表如下:


  • 搜索功能


  • 新增/修改标签功能


  • 新增/修改网址功能


  • 导入配置功能


  • 导出配置功能


  • 编辑功能


开始抽出代码


上述的每一个功能都会通过一个JS文件去存储该功能对应的变量以及方法。然后所有的JS文件都是放在src/use下的,如图


ddbd383b897f2847b3cfce1568de9139.png


就拿 新增/修改标签功能 来举例子,用一个动图给大家看看该功能的全部效果


3d5a938d18d957601a07c9e805aaea66.jpg


很明显,我是做了一个弹窗组件,当点击侧边栏中的 + 号后,弹窗显示;然后我输入了想要新增标签的名称,并且选择了合适的图标,最后点击了确认,于是一个标签就添加好了,弹窗也随之隐藏;


最后我又去编辑模式下点击修改标签,弹窗再次显示,与此同时把对应标签的名称与图标都渲染了出来;待我修改了名字后,点击了确认,于是标签的信息就被我改好了,弹窗又随之隐藏了。


所以总结一下涉及到的功能就有以下几个:


  1. 弹窗的展示
  2. 弹窗的隐藏
  3. 点击确认后新增或修改标签内容


按照传统的写法,实现上述三个功能是这个样子的(我修改并简化了代码,大家理解意思就行):


  • 侧边栏组件内容


<!-- 侧边栏组件内容 -->
<template>
    <aside>
     <div @click="show">新增标签</div>
        <tab-alert :isShow="isShow" @closeTabAlert="close"/>
    </aside>
</template>
<script>
import { ref } from 'vue'
import tabAlert from '@/components/tabAlert/index'
export default {
    name: "tab",
    components: {
     tabAlert
    },
    setup() {
     // 存储标签弹框的展示情况
     const isShow = ref(false)   
        // 展示标签弹框
        function show() {
            isShow.value = true
        }
        // 隐藏标签弹框
        function close() {
            isShow.value = false
        }
        return { isShow, show, close }
    }
}
</script>


  • 标签弹框组件内容


<!-- 标签弹框组件内容 -->
<template>
    <div v-show="isShow">
     <!-- 此处省略一部分不重要的内容代码 -->
        <div @click="close">取消</div>
        <div @click="confirm">确认</div>
    </div>
</template>
<script>
export default {
    name: "tab",
    props: {
     isShow: {
            type: Boolean,
            default: false
        }
    },
    setup(props, {emit}) {
     // 隐藏标签弹框
     function close() {
            emit('close')
        }
        // 点击确认后的操作
        function confirm() {
            /* 此处省略点击确认按钮后更新标签内容的业务代码 */
            close()
        }
        return { close, confirm }
    }
}
</script>


看完了我上面举例的代码后可以发现,简简单单的一个功能的实现,却涉及到两个组件,而且还需要父子组件相互通信来控制一些状态,这样不就把功能打散了嘛,即不够聚合。所以按照功能来抽离这些功能代码时,我会为他们创建一个 tabAlert.js 文件,里面存储着关于这个功能所有的变量与方法。


tabAlert.js文件中的大致结构是这样的:


// 引入依赖API
import { ref } from 'vue'
// 定义一些变量
const isShow = ref(false)     // 存储标签弹框的展示状态
export default function tabAlertFunction() {
    /* 定义一些方法 */
    // 展示标签弹框
    function show() {
     isShow.value = true
    }
    // 关闭标签弹框
    function close() {
     isShow.value = false
    }
    // 点击确认按钮以后的操作
    function confirm() {
        /* 此处省略点击确认按钮后更新标签内容的业务代码 */
        close()
    }
    return {
     isShow,
        show,
        close,
        confirm,
    }
}


对于为何设计这样的结构,先从导出的方法来说,我把跟该功能相关的所有方法放在了一个函数中,最后通过return导出,是因为有时候这些方法会依赖于外部其它的变量,所以用函数包裹了一层,例如:


// example.js
export default function exampleFunction(num) {
    function log1() {
     console.log(num + 1)
    }
    function log2() {
     console.log(num + 2)
    }
    return {
     log1,
     log2,
    }
}


从这个文件中我们发现,log1log2方法都是依赖于变量num的,但我们并没有在该文件中定义变量num,那么可以在别的组件中引入该文件时,给最外层的exampleFunction方法传递一个参数num即可


<template>
    <button @click="log1">打印加1</button>
    <button @click="log2">打印加2</button>
</template>
<script>
import exampleFunction from './example'
import { num } from './getNum'  // 假设num是从别的模块中获取到的
export default {
    setup() {
     let { log1, log2 } = exampleFunction(num)
        return { log1, log2 }
    }
}
</script>


然后再来说说为什么变量的定义在我们导出函数的外部。再继续看我上面举的我项目中标签页功能的例子吧,用于存储标签弹框展示状态的变量isShow是在某个组件中定义的,同时标签组件也需要获取这个变量来控制展示的状态,这之间用到了父子组件通信,那么我们不妨把这个变量写在一个公共的文件中,无论哪个组件需要用到的时候,只需要导入获取就好了,因为每次获取到的都是同一个变量


7734312804dfbb823123e653e1a47027.png


这样一来,岂不是连父子组件通信都省了嘛?


我们把刚刚封装好的tabAlert.js用到组件中去,看看是什么效果


  • 侧边栏组件内容


<!-- 侧边栏组件内容 -->
<template>
    <aside>
     <div @click="show">新增标签</div>
        <tab-alert/>
    </aside>
</template>
<script>
import tabAlert from '@/components/tabAlert/index'
import tabAlertFunction from '@/use/tabAlert'
export default {
    name: "tab",
    components: {
     tabAlert
    },
    setup() {
        let { show } = tabAlertFunction()
        return { show }
    }
}
</script>


  • 标签弹框组件内容


<!-- 标签弹框组件内容 -->
<template>
    <div v-show="isShow">
     <!-- 此处省略一部分不重要的内容代码 -->
        <div @click="close">取消</div>
        <div @click="confirm">确认</div>
    </div>
</template>
<script>
import tabAlertFunction from '@/use/tabAlert'
export default {
    name: "tab",
    setup() {
        let { isShow, close, confirm } = tabAlertFunction() 
        return { isShow, close, confirm }
    }
}
</script>


这时候再翻上去看看最初的代码,有没有感觉代码抽离后,变得非常规整,而且组件中少了很多的代码量。


这样通过功能来将变量和代码聚集在一起的方法,我个人认为是比较好管理的,倘若之后有一天想在该功能上新增什么小需求,只要找到tabAlert.js这个文件,在里面写方法和变量即可


展示环节


我就是按照这样的方法,对我原本的代码进行了抽离,下面给大家看几组抽离前抽离后的代码对比

相关文章
|
6月前
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
264 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
3月前
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
105 0
|
7月前
|
人工智能 JavaScript 关系型数据库
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
221 14
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
|
7月前
|
前端开发 JavaScript Java
【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
319 13
【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
|
7月前
|
SQL JavaScript 安全
【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
264 11
【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
|
7月前
|
人工智能 JavaScript 安全
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
271 13
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
|
7月前
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
278 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
|
6月前
|
资源调度 JavaScript 前端开发
Pinia 如何在 Vue 3 项目中进行安装和配置?
Pinia 如何在 Vue 3 项目中进行安装和配置?
468 4
|
6月前
|
人工智能 Java API
ai-api-union项目,适配各AI厂商api
本项目旨在实现兼容各大模型厂商API的流式对话和同步对话接口,现已支持智谱、豆包、通义、通义版DeepSeek。项目地址:[https://gitee.com/alpbeta/ai-api-union](https://gitee.com/alpbeta/ai-api-union)。通过`ChatController`类暴露两个接口,入参为`ChatRequest`,包含会话ID、大模型标识符和聊天消息列表。流式对话返回`Flux&lt;String&gt;`,同步调用返回`String`
|
7月前
|
自然语言处理 API 开发者
DeepSeek-Free-API:DeepSeekV3免费的api接口,需要使用api方式的同学可以参考一下这个项目,可以收藏起来试一下
嗨,大家好,我是小华同学。今天为大家介绍一个开源项目——DeepSeek V3 Free 服务。该项目基于 DeepSeek-V3 R1 大模型,提供免费、高性能的 API,支持高速流式输出、多轮对话、联网搜索和深度思考等功能。适用于智能客服、内容创作、教育辅助等场景。部署方式灵活,支持 Docker、Docker-compose、Render、Vercel 和原生部署。欢迎关注我们,获取更多优质开源项目和高效工作学习方法。
2128 15