最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 提升项目档次——封装

    正文概述 掘金(追旅)   2021-06-09   406

    更多文章

    前言

    项目开发是小伙伴们的家常便饭,追旅也参与了很多项目的开发,抛去大厂基本都是在做傻瓜式式开发,追旅大多数时候也是在着傻瓜式的开发,例如用umijsvue-cli或者react的各种脚手架(我们不谈这些脚手架的优劣),参考脚手架的api基本上也就可以快速启动一个项目,相当的方便,这让开发者可以把更多的精力投入业务开发中去,所以想要在这块有所突破要么webpack自行撸一套脚手架,要么想尤大大那样创造新大陆vite,追旅这里分享一下更实际的业务开发,如何更好的落地组件化模块化

    UI层

    ui层组件的抽离,相信大家都做过很多了

    基础组件

    element-uiantd-design等组件库中你看得到的均属于基础组件,组件库不包含的基础组件也是基础组件,只是要自己去封装,对于绝大多数场景来说这些组件已经是最小单位了,但是在实际项目中基础组件也是需要进行业务封装的,原因如下:

    1. 基础组件在书写业务时也会导致大量的重复性代码和功能
    2. 基础组件在组装成业务组件时也有可能因为不满足需求需要拓展
    3. ui层的操作不变可以通过控制数据层影响ui层

    tableformdemo(后续会使用到):

    form

    // form.vue
    // 根据实际业务去适当的封装form
    // 后续表单类提交的均使用form组件
    // 不必一次性封装完毕,遇见差异元素添加
    <template lang="pug">
    el-form(
      ref="formRef"
      class="form-box"
      :model="formData"
      :label-position="labelPosition"
    )
      el-row(
        :gutter='20'
        :class="inline ? '' : 'form-column'"
      )
        el-col(
          :span="inline ? item.span ? item.span : 4 : 24"
          v-for="item in formConfig"
          :key="item.key"
        )
          el-form-item(
            style="width: 100%;"
            :label="item.label"
            :prop="item.key"
          )
            //- input
            el-input(
              style="width: 100%;"
              v-if="item.type === 'input'"
              v-model="formData[item.key]"
              :placeholder="item.placeholder ? item.placeholder: '请输入'"
              :disabled="item.disabled ? item.disabled : false"
              :type="item.kind ? item.kind : 'text'"
            )
            //- select
            el-select(
              style="width: 100%;"
              v-if="item.type === 'select'"
              v-model="formData[item.key]"
              :disabled="item.disabled ? item.disabled : false"
              :placeholder="item.placeholder ? item.placeholder: '请选择'"
            )
              el-option(
                v-for="child in item.options"
                :key="item.otherKeys ? child[item.otherKeys.value] : child.value"
                :label="item.otherKeys ? child[item.otherKeys.label] : child.label"
                :value="item.otherKeys ? child[item.otherKeys.value] : child.value"
              )
        div(:class="inline ? 'form-sumbit' : ''")
          slot
    </template>
    <script>
    import { ref } from 'vue'
    export default {
      props: {
        formData: {
          type: Object,
          default: () => ({})
        },
        formConfig: {
          type: Array,
          default: () => ([])
        },
        inline: {
          type: Boolean,
          default: true
        },
        labelPosition: {
          type: String,
          default: 'top'
        }
      },
      setup() {
        const formRef = ref()
    
        const submitHandle = () => {
          return new Promise((resolve, reject) => {
            formRef.value.validate(valid => {
              valid && resolve(valid)
              !valid && reject(valid)
            })
          })
        }
    
        const resetHandle = () => formRef.value.resetFields()
    
        const getForm = () => formRef.value
    
        return {
          formRef,
          submitHandle,
          resetHandle,
          getForm
        }
      }
    }
    </script>
    

    table

    // table.vue
    <template lang="pug">
    el-table(:data="dataSource", v-loading="loading")
      el-table-column(
        v-for="(item, index) in columns"
        :key="item.key ? item.key : index"
        :label="item.label"
        :width="item.width"
        :fixed="item.fixed ? item.fixed : false"
      )
        template(slot-scope="scope")
          span(v-if="item.key !== 'operate'") {{ item.render ?  item.render(scope.row, scope.row[item.key]) : scope.row[item.key] }}
          div(v-if="item.key === 'operate'")
            el-button(
              v-for="(btn, index) in item.options"
              :key="index"
              type="text"
              @click="() => buttonHandle({row: scope.row, label: btn.render ? btn.render(scope.row) : btn.label})"
            ) {{ btn.render ? btn.render(scope.row) : btn.label }}
    
    </template>
    <script>
    export default {
      props: {
        dataSource: {
          type: Array,
          default: () => ([])
        },
        columns: {
          type: Array,
          default: () => ([])
        },
        loading: Boolean
      },
      setup(props, { emit }) {
        const buttonHandle = (detail) => {
          emit('buttonHandle', detail)
        }
    
        return {
          buttonHandle
        }
      }
    }
    </script>
    

    业务组件

    一次性的业务组件可以一次性封装,但绝大多数场景不是这样的,还是以formtable为例,不单单是只有列表查询的场景会使用到formtable新增添加等场景会用到form,对于其他场景亦会用到table,假设我们将上述两个基础组件放在一起没有做抽离,我也只有列表查询一个固定场景,而对于其他表单提交场景或者列表展示场景则需要额外的再去写一个form或者table组件,这样导致的问题是:

    1. 场景单一、无法服用
    2. 业务组件中的基础组件无法复用,利用度不高
    3. 对于样式统一度较高的平台不利于统一

    所以结论就是:

    业务组件 += 基础组件

    业务组件必定是基础组件组装的(除非你能保证你永远不在用着业务组件中的基础组件),最终查询表单场景ui层组件代码如下(稍后讨论逻辑层):

    <template lang="pug">
    .list
      Form(
        ref="formRef"
        :formData="formData",
        :formConfig="formConfig"
      )
        el-button(type="primary", @click="searchFormHandle") 搜索
        el-button(@click="resetFormHandle") 重置
      Table(:dataSource="dataSource", :columns="columns", :loading="loading")
      Pagination(:pagination="pagination")
    </template>
    

    hooks-逻辑层

    react16+hooks刚出来的时候追旅在的公司刚好也是react的,怀着激动的心情去尝试了一下,当时的感受就是逻辑抽离从未如此简单,之后就是尤大大的vue3.x也采用类似的方案,组合式API是两大框架最直观的特点,这极大的方便了我们对逻辑层的抽离,继续回到我们的主题

    这里顺便讲一下基础逻辑和工具的区别,hooks的本质是对公共逻辑的抽离并暴露出相应的api,而工具如时间处理函数、数字转千分位格式等依然是属于utils

    依然是tableformdemo

    useTbale

    // useTable.js
    // 如果你了解ts,建议使用ts
    import { reactive, toRefs, ref } from 'vue'
    
    /**
     * @param request 接口或Array<any>
     * @param defaultParams 默认入参
     * @function isInit 是否初始化调用
     */
    
    function useTable(
      request,
      defaultParams,
      isInit = true
    ) {
      const c = defaultParams && defaultParams.current ? defaultParams.current : 1
      const p = defaultParams && defaultParams.pageSize ? defaultParams.pageSize : 10
    
      const current = ref(c)
      const pageSize = ref(p)
    
      const state = reactive({
        params: Object.assign({ pageNo: current, pageSize: pageSize }, defaultParams),
        searchInfo: {},
        dataSource: [],
        loading: false,
        pagination: {
          current: current,
          pageSize: pageSize,
          total: 0,
          onChange: (page, pageSize) => pageChange(page, pageSize),
          onShowSizeChange: (current, size) => showSizeChange(current, size)
        }
      })
      // api请求
      const _request = (params) => {
        // json
        if (Object.prototype.toString.call(request) === '[object Array]') {
          state.dataSource = request
          state.pagination.total = request.length
        }
        // api 请求
        if (Object.prototype.toString.call(request) === '[object Function]') {
          state.loading = true
          request({ ...state.params, ...params }).then((res = {}) => {
            state.loading = false
            const { data: { code, data } } = res
            if (code === '0') {
              const { totalCount, pagedRecords } = data || {}
              state.pagination.total = totalCount || pagedRecords.length
              state.dataSource = pagedRecords
            }
          })
        }
      }
      // 查询
      const searchHandle = (searchInfo) => {
        current.value = c
        pageSize.value = p
        if (searchInfo.pageNo) {
          state.pagination.current = searchInfo.pageNo
        }
        if (searchInfo.pageSize) {
          state.pagination.pageSize = searchInfo.pageSize
        }
        state.searchInfo = searchInfo
        _request(searchInfo)
      }
      // 切换页数
      const pageChange = (page, pageSize) => {
        current.value = page
        state.searchInfo.pageNo = page
        _request(state.searchInfo)
      }
      // 切换条数
      const showSizeChange = (current, size) => {
        pageSize.value = current
        state.searchInfo.pageSize = current
        _request(state.searchInfo)
      }
      // 重置
      const resetHandle = () => {
        state.searchInfo = {}
        current.value = c
        pageSize.value = p
        _request({})
      }
      // 初始化数据
      isInit && _request(state.params)
    
      return {
        ...toRefs(state),
        searchHandle,
        resetHandle
      }
    }
    
    export default useTable
    
    

    useForm

    // useForm.js
    // 只针对追旅目前遇到的业务做相应封装
    import { reactive, toRefs } from 'vue'
    
    export default ({
      formRef,
      formData,
      formConfig
    }) => {
      const state = reactive({
        formData,
        formConfig
      })
      // 提交表单
      const submitHandle = (callback) => {
        const { value: { submitHandle } } = formRef
        submitHandle().then(() => {
          callback(state.formData)
        })
      }
      // 重置表单
      const resetHandle = () => formRef.value.resetHandle()
      // 设置formConfig-options,异步下拉框等
      const setOptions = (key, options = []) => {
        const item = state.formConfig.find(item => item.key === key)
        if (!item) return
        item.options = options
      }
      // 查询formConfig-item
      const getConfig = (key) => state.formConfig.find(item => item.key === key)
    
      return {
        submitHandle,
        resetHandle,
        setOptions,
        getConfig,
        ...toRefs(state)
      }
    }
    

    使用

    <template lang="pug">
    div
      Form(
        ref="formRef"
        :formData="formData",
        :formConfig="formConfig"
      )
        el-button(type="primary", @click="searchFormHandle") 搜索
        el-button(@click="resetFormHandle") 重置
      Table(:dataSource="dataSource", :columns="columns", :loading="loading")
      Pagination(:pagination="pagination")
    </template>
    <script>
    import { ref } from 'vue'
    import Form from '@/components/qjd/form'
    import Table from '@/components/qjd/table'
    import Pagination from '@/components/qjd/pagination'
    import useForm from '@/hooks/useForm'
    import useTable from '@/hooks/useTable'
    import { getSearchList } from '@/api/list'
    
    export default {
      components: {
        Form,
        Table,
        Pagination,
      },
      setup(props, { root }) {
        const formRef = ref()
        const columns = ref([
          { label: '姓名', key: 'name' },
          { label: '性别', key: 'sex' },
          { label: '年龄', key: 'age' },
          { label: '身高', key: 'height' },
        ])
        // form
        const {
          formData,
          formConfig,
          submitHandle,
          resetHandle,
        } = useForm({
          formRef,
          formData: { name: undefined, sex: undefined },
          formConfig: [
            { type: 'input', key: 'name', label: '姓名', span: 6 },
            { type: 'input', key: 'sex', label: '性别', span: 6 }
          ]
        })
    
        // table & page
        const {
          loading,
          dataSource,
          pagination,
          searchHandle,
          resetHandle: resetTableHandle
        } = useTable(getSearchList, {})
        // 查询
        const searchFormHandle = () => submitHandle(searchHandle)
        // 重置
        const resetFormHandle = () => {
          resetHandle()
          resetTableHandle()
        }
    
        return {
          formRef,
          formData,
          formConfig,
          searchFormHandle,
          resetFormHandle,
          loading,
          dataSource,
          pagination,
          columns
        }
      },
    }
    </script>
    

    尽管场景简单,但这也足以让组合式API优势尽显

    高内聚、低耦合

    高内聚、低耦合是个经久不衰的话题,低耦合就是拆分的过程,高内聚就是组装的过程,一辆可以跑起来的车是由无数个零件组装起来的,如果可以把组装和拆分做好,项目的可维护度、可读性就变得非常的高,各大框架核心依然是组件化、模块化,当开发者做的足够好时tslinteslint等辅助性工具也只是锦上添花而已,不知道你是否见到过2000行的ts代码?组件化、模块化是种思想,这种思想深入人心的同时,是否可以将这种思想落地的更好

    总结

    封装是门艺术


    起源地下载网 » 提升项目档次——封装

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元