美文网首页
前端组件抽取

前端组件抽取

作者: buffonme | 来源:发表于2022-07-20 11:48 被阅读0次

    前言

    纯菜鸡记录

    业务场景

    [图片上传失败...(image-ed53dd-1658375271104)]

    • 拆分思路:
    1. 根据业务,可以分成 nav(面包屑+树状导航)、page(内容展示);根据内容展示的不同,还可以继续拆分
    2. 根据项目结构,可以分为xxx.vue(页面)、data_xx.ts(接口)、use_xx.ts(逻辑),还有其他文件,如常量管理、路由管理、鉴权管理等
    • 拆分方向:

    拆分足够细,尽量使用函数式编程(有很多方向,只是选择了其中的一种)

    • 以拆分nav(面包屑+树状导航)为例

    文件结构:

    [图片上传失败...(image-670478-1658375271104)]

    /*
    * bread-crumb.vue
    * 面包屑页面:包含页面需要的数据以及跳转功能
    * data_nav.ts
    * nav(面包屑+树状导航)相关接口文件及简单数据处理
    * index.vue
    * nav(面包屑+树状导航)入口页面(布局、提供各个子页面所需的数据
    * interface.ts
    * typescript相关接口定义等
    * tree.vue和tree_menu.vue
    * 树状导航:包含页面需要的数据以及跳转功能
    * use_base.ts
    * 组装全局所需的一些字段,如 id 等
    * use_nav.ts
    * 组装nav(面包屑+树状导航)所需的数据
    * use_router.ts
    * 跳转功能封装
    */
    
    • 不同业务模块之间的数据交互(在交互设计上,需要尽可能做到功能分离化)

    在nav(面包屑+树状导航)模块定义:

    /**
     * use_base.ts
     * 基本信息钩子:暴露了基本数据base,并且provide了可供全局使用的数据和方法
     */
    import { provide, computed } from 'vue'
    import { useRoute, useRouter } from 'vue-router'
    import { BASE, BASE_ID_OPEN } from './constant'
    
    function fmtId(v?: string) {
      return v || ''
    }
    
    
    export function useBase() {
      const router = useRouter()
      const route = useRoute()
    
    
      const base = computed(() => {
        return {
          id: fmtId(route.query.id as string),
          parentId: fmtId(route.query.parentId as string)
        }
      })
    
      provide(BASE, base)
    
      provide(BASE_ID_OPEN, (id: string) => {
        router.push({ query: { id } })
      })
    
    
      return {
        base
      }
    }
    
    

    在内容展示模块使用:

    // use_main.ts
    import { computed, ComputedRef, inject, watch } from 'vue'
    import { BASE, CHAIN, BASE_ID_OPEN } from './constant'
    import { TribeData } from './interface'
    
    export function useMain() {
      const base = inject<ComputedRef<{ id: string, parentId: string }>>(BASE, computed(() => ({
        id: '',
        parentId: ''
      })))
    
      const openId = inject<(id: string) => void>(BASE_ID_OPEN)
    
      const updateChain = (name: string) => {
        const lastChainItem = chain.value[chain.value.length - 1]
        lastChainItem.label = name
      }
    
      return {
        base,
        openId,
        chain,
        updateChain
      }
    }
    

    这样,在内容展示模块就可以使用这些基本信息和方法了。

    补充:
    最近遇到了这样一个场景(如下图)

    [图片上传失败...(image-4f9ae9-1658375271104)]

    页面上的表格和弹窗中的表格其实会调同一个接口,由于页面上可操作这个表格,进行新增、编辑和删除等,而弹窗中的表格是全量数据(先不谈展示优化)

    因此,为保证弹窗中的表格数据要与页面的保持一致,需要每弹出一次弹窗就要调一下全量数据的接口。

    但是根据需求分析,有一些状态下,页面表格是不可操作状态的,也就意味着,弹窗数据不会发生变化,此时,就可以将请求返回的数据缓存起来,不必每次点击查看就发起请求了。

    // use_cache.ts
    
    import { computed, ref, watch, reactive } from 'vue'
    
    import { getXXX } from './data_XXX'
    import { showError } from '@/XXX'
    import type { IXXX } from './interface'
    
    // 页面数据缓存池
    export const poolList: Record<number, IXXX[]> = {}
    
    // 用于显示更多信息
    export function useCache() {
      // 是否发请求
      const noCache = ref<boolean>(true)
      // 区分数据缓存池中不同数据的标识
      const cacheId = ref<number | undefined>()
    
      const cache = reactive<Array<ISpecFunc>>([])
    
      async function echo() {
        const id = cacheId.value
        if (id === undefined) { return }
        if (noCache.value && !(poolList[id])) {
          try {
            const ret = await getXXX({
              id,
              current: -1
            })
            poolList[id] = ret.list
            Object.assign(cache, ret.list)
          } catch (err) {
            showError(err)
          }
        }
      }
    
      return {
        noCache,
        cacheId,
        cache,
        echo
      }
    }
    
    
    // 弹窗
    <script lang="ts" setup>
    
    import { watch, computed } from 'vue'
    
    import { useCache } from './use_cache'
    
    const { noCache, cacheId, cache, echo } = useSpecCache()
    
    const props = defineProps<{
      noCache: boolean
      specId: number | undefined
      visible: boolean
    }>()
    watch(
      () => props.visible,
      (val) => {
        if (val) {
          noCache.value = props.noCache
          cacheId.value = props.specId as number
          echo()
        }
      }
    )
    const isVisible = computed(() => props.visible)
    
    const emit = defineEmits<{
      (event: 'close'): void,
    }>()
    
    const onclose = () => {
      emit('close')
    }
    </script>
    
    <template>
      <el-dialog
        v-model="isVisible"
        width="860px"
        @close="onclose"
      >
          ...
      </el-dialog>
    </template>
    
    

    如果后台配合的话,还可以做的更多,比如后台返回一个数据id,记录这份数据的更新,每一次更新都会改变这个字段,这样就可以将这个字段与cacheId组合起来区分数据缓存池中的不同数据

    相关文章

      网友评论

          本文标题:前端组件抽取

          本文链接:https://www.haomeiwen.com/subject/ygyfurtx.html