背景

在新旧体系的Vue3和2工程中,由于组件封装和Vue版本的差异,导致我们在定义类似功能的组件时,会导致原有界面需要重新,不能快速的迁移旧页面,而以下即是一个方案,使用中间件组件过滤连接对应的参数.

总结vue2和3的在书写方面的差异性

  • 相关插槽的写法变化

  • $on 在vue3中废止

过渡组件定义

// mpage.vue
<script lang="jsx">
  import useDataSource from '@/hooks/useDataSource.js'
  import { onMounted, shallowRef } from 'vue'
  const props = {
    isPage: {
      type: Boolean,
      default: false
    },
    title: {
      type: String,
      default: ''
    },
    defaultTabName: {
      type: String,
      default: ''
    },
    titleIco: {
      type: [String, Boolean],
      default: false
    },
    isSearch: {
      type: Boolean,
      default: false
    },
    isAdd: {
      type: Boolean,
      default: false
    },
    isClear: {
      type: Boolean,
      default: false
    },
    clearAction: {
      type: String,
      default: ''
    },
    isEdit: {
      type: Boolean,
      default: false
    },
    forms: {
      type: [Boolean, Array],
      default: false
    },
    actions: {
      type: Array,
      default: () => []
    },
    actionSort: {
      type: Array,
      default: () => []
    },
    addAction: {
      type: String,
      default: ''
    },

    formColumn: {
      type: Number,
      default: 4
    },
    formModel: {
      type: [Boolean, Object],
      default: false
    },
    tables: {
      type: [Boolean, Object, Array],
      // ex: {
      //   name: 'table',
      //   columns: [],
      //   data: 'tabledata'
      // },
      // ex: [
      //   {
      //     name: 'table',
      //     title:'头信息'
      //     label:''
      //     columns: [],
      //     data: 'tabledata'
      //   },
      //   {
      //     name: 'linetable',
      //      title:'行信息'
      //     columns: [],
      //     data: 'tabledata'
      //   }
      // ],
      default: false
    },
    tableActions: {
      type: Array,
      default: () => []
    },
    orderData: {
      type: Object,
      default: () => {
        return {}
      }
    },
    loading: {
      type: Object,
      default: () => {
        return {}
      }
    },
    createLoad: {
      type: Boolean,
      default: false
    },
    labelWidth: {
      type: [Boolean, String],
      default: '80px'
    },
    formDisabled: {
      type: Boolean,
      default: false
    }
  }
  const seqCol = [
    {
      type: 'seq',
      fixed: 'left'
    }
  ]
  export default defineComponent({
    name: 'mpage',
    props,
    setup(propData, { slots, expose }) {
      const { dataSource, dataSourceDispatch } = useDataSource({})
      const formItems = shallowRef([])
      onMounted(() => {
        formItems.value = propData.forms
          .filter((item) => item)
          .map((item) => {
            let type = item.type

            if (item.option) {
              if (item.option.type && item.option.type.includes('range')) {
                type = item.option.type
              }
              if (item.option.list && typeof item.option.list === 'string') {
                dataSourceDispatch({
                  [item.prop]: {
                    type: 'lov',
                    params: {
                      code: item.option.list
                    },
                    useId: item.useId
                  }
                })
              }
            }

            return {
              label: item.label,
              prop: item.prop,
              type,
              options: {
                list: () => dataSource[item.prop] || []
              }
            }
          })
      })

      const actions = computed(() => {
        return propData.actions.map((item) => {
          if (!item.operation) {
            return {
              text: item.text,
              click() {
                if (item.click) {
                  return item.click()
                }
              }
            }
          }
          return {
            text: item.text
          }
        })
      })

      const tableConfig = computed(() =>
        propData.tables.map((item) => {
          return {
            title: item.label,
            tabKey: item.name,
            columns: seqCol.concat(
              item.columns.map((item) => {
                return {
                  title: item.label,
                  field: item.prop,
                  params: {
                    form: item.form
                  }
                }
              })
            ),
            data: item.data
          }
        })
      )
      expose({ // 对外暴露
        reloadPageData() {}
      })
      return () => (
        // 对应新组件
        <c-grid
          {...{
            options: {
              query: propData.isSearch,
              formConfig: {
                split: 4,
                items: formItems
              },
              actions: unref(actions),
              tableConfig: unref(tableConfig)
            },
            pageData: propData.formModel,
            title: propData.title
          }}>
        </c-grid>
      )
    }
  })
</script>
<style lang="scss"></style>

// index.vue 旧组件
<template>
  // 将vue2的组件cpage 改为了 mpage
  <mpage  
    isSearch
    isClear
    labelWidth="110px"
    ref="cpage"
    :formModel="queryParam"
    :loading="pageLoading"
    :title="$t('action.search')"
    :forms="[
      {
        label: $t('page.DomesticOrOverseas'),
        prop: 'isOversea',
        type: 'select',
        option: {
          useId: false,
          list: 'Domestic/Oversea'
        }
      },
      {
        label: $t('page.Product family'),
        prop: 'productFamily',
        type: 'select',
        option: {
          useId: false,
          list: 'Family_Code'
        }
      },
      {
        prop: 'versions',
        label: () => $t('page.powerVersion'),
        type: 'select',
        option: {
          list: 'POWER VERSION',
          useId: false
        }
      },
      {
        label: $t('page.month'),
        prop: 'month',
        type: 'date',
        option: {
          type: 'monthrange',
          valueFormat: 'yyyyMM',
          format: 'yyyyMM'
        }
      }
    ]"
    :actions="[
      {
        text: $t('cp.results generated'),
        click: handleResult
      },
      {
        text: '测算版结果生成',
        click: handleResult2
      },
      {
        text: '保存版本',
        loading: pageLoading.btn,
        click: handleSaveVersion
      },
      {
        text: '同步报价',
        loading: pageLoading.btn,
        click: handleResult
      },
      {
        text: '同步预算',
        loading: pageLoading.btn,
        click: handleResult
      },
      {
        operation: 'exports',
        api: {},
        options: {
          params: queryParam,
          column: tableCol
        }
      }
    ]"
    :tables="[
      {
        label: '明细查询',
        name: 'info',
        tableAction: false,
        tableActionWidth: 120,
        createLoad: true,
        columns: tableCol,
        data: handSearchData
      }
    ]"
  >
    <template v-slot:table_action_info="nowTableData, table">
      <c-s-table :table="table" title="table"></c-s-table>
    </template>
  </mpage>
</template>
<script>
  import mpage from './mpage.vue'
  export default {
    name: '功率预测计算',
    components: {
      mpage
    },
    data() {
      return {
        pageLoading: {
          page: false,
          btn: false,
          results: false
        },
        visible: false,
        rowData: {},
        model: {
          editions: undefined
        },
        monthIndex: 0,
        monthQIndex: 0,
        colData: [],
        queryParam: {},
        tableTab: 'info'
      }
    },
    computed: {
      tableCol() {
        const self = this
        return [
          {
            label: () => self.$t('page.DomesticOrOverseas'),
            prop: 'isOverseaName',
            minWidth: 100,
            needFilter: true
          },
          {
            label: () => self.$t('page.Product family'),
            prop: 'productFamilyName',
            minWidth: 100,
            needFilter: true
          },
          {
            prop: 'resemblanceProductFamily',
            label: () => self.$t('cp.Same power product family'),
            minWidth: 100,
            needFilter: true,
            form: {
              type: 'input'
            }
          },
          {
            prop: 'status',
            label: '产品族状态',
            minWidth: 100,
            needFilter: true,
            form: {
              type: 'input'
            }
          },
          {
            prop: 'versionsName',
            label: () => self.$t('page.powerVersion'),
            minWidth: 100,
            needFilter: true
          },
          {
            label: () => self.$t('page.Somehow the outfit'),
            prop: 'installTypeName',
            minWidth: 100,
            needFilter: true
          },
          {
            label: () => '档位',
            prop: 'tapPosition',
            minWidth: 100,
            needFilter: true
          }
        ].concat(this.colData)
        // .concat([
        //   {
        //     label: self.$t('焊带'),
        //     prop: 'itemAttribute1',
        //     minWidth: 100
        //   },
        //   {
        //     label: self.$t('前玻璃'),
        //     prop: 'itemAttribute2',
        //     minWidth: 100
        //   },
        //   {
        //     label: self.$t('lrf'),
        //     prop: 'itemAttribute3',
        //     minWidth: 100
        //   },
        //   {
        //     label: self.$t('eva'),
        //     prop: 'itemAttribute4',
        //     minWidth: 100
        //   },
        //   {
        //     label: self.$t('后玻璃'),
        //     prop: 'itemAttribute5',
        //     minWidth: 100
        //   },
        //   {
        //     label: self.$t('反光汇流条'),
        //     prop: 'itemAttribute6',
        //     minWidth: 100
        //   },
        //   {
        //     label: self.$t('汇流条厚度'),
        //     prop: 'itemAttribute7',
        //     minWidth: 100
        //   }
        // ])
      },
      forms() {
        return [
          {
            label: () => '版本',
            prop: 'edition',
            type: 'input',
            rules: {
              required: true,
              message: () => '请输入版本'
            }
          }
        ]
      }
    },
    methods: {
      handSearchData(params) {
        this.colData = []
        const month = {}

        if (this.queryParam.month) {
          month.beginMonth = this.queryParam.month[0]
          month.endMonth = this.queryParam.month[1]
        }

        this.queryParam.summaryType = 'PERCENT_RELEASE'

        return this.$service.lov.page({
          data: Object.assign({}, params, this.queryParam)
        })
      },
      handleResult() {
        const month = {}

        if (this.queryParam.month) {
          month.beginMonth = this.queryParam.month[0]
          month.endMonth = this.queryParam.month[1]
        }

        return this.$service.lov
          .page({
            data: Object.assign({}, this.queryParam)
          })
          .then((result) => {
            if (result.msg === 'success') {
              this.pageLoading.results = false
              this.$message.success('生成结果成功')
              this.$refs.cpage.reloadPageData()
            } else {
              this.pageLoading.results = false
              this.$message.error(result.msg)
            }

            return
          })
      },
      handleResult2() {
        const month = {}

        if (this.queryParam.month) {
          month.beginMonth = this.queryParam.month[0]
          month.endMonth = this.queryParam.month[1]
        }

        return this.$service.aps.powerPrediction.report.powerResultLong
          .calculation({
            ...this.queryParam,
            ...month,
            dataVersion: '测算版'
          })
          .then((result) => {
            if (result.msg === 'success') {
              this.pageLoading.results = false
              this.$message.success('生成结果成功')
              this.$refs.cpage.reloadPageData()
            } else {
              this.pageLoading.results = false
              this.$message.error(result.msg)
            }

            return
          })
      },
      // 保存版本
      handleSaveVersion() {
        this.visible = true
      },
      handleConfirm() {
        this.$service.aps.powerPrediction.report.powerResultLong.saveEdition(this.model).then((result) => {
          if (result.msg === 'success') {
            this.$message.success('生成结果成功')
            this.$refs.cpage.reloadPageData()
            this.visible = false
          } else {
            this.$message.error(result.msg)
          }
        })
      }
    }
  }
</script>

附部分Vue3写法说明

1.unref()

如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x)
  // unwrapped 现在保证为 number 类型
}

2.toValue()

将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。

toValue(1) //       --> 1
toValue(ref(1)) //  --> 1
toValue(() => 1) // --> 1

对比Vue3和vue2的差异

区别

vue3

vue2

源码

源码使用typescript进行重构,vue对typescript支持更加友好了

javascript 使用flow进行类型检测

性能

使用Proxy来实现数据劫持,删除了一些api($on,$once,$off) fiter等,优化了Block tree,solt,diff 算法等

使用object.defineProperty来劫持数据的setter和getter方法,对象改变需要借助api去深度监听

api方面

CompositionAPI 将模块相关代码统一放在一个地方处理,不需要在多个options里查找

OptionsAPI 在options里写data,methods,created等描述组件对象,多个逻辑可能在不不同地方,代码内聚性低

hook函数增加代码复用性

vue3可以通过hook函数 将一部分独立的逻辑抽离出去,并且也是响应式的

vue2使用mixins进行代码逻辑共享,mixins也是由一大堆options组成,如果有多个mixins则可能造成命名冲突等问题

代码写法方面

vue3支持在template中写多个根,vue2只能有一个

当内部有异步函数,需要使用到await的时候,可以直接使用,不需要在setup前面加async

掘金补充说明