• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

「Vue3实践」- vue3两种API方式+TSX实现弹框表格组件

武飞扬头像
yinmochunCoder
帮助401

为了清晰的描述,如何实现一个弹框表格组件,我将按照组件的开发过程来讲解,即需求理解/拆分->组件定义->组件实现->组件应用

一、业务需求

项目中涉及多个业务子模块,不同的子模块中都有对表单操作,其中有个表单项,是一个input文本框,该文本框与一般的文本框不同,不用于手动输入数据,而仅仅用于展示数据。展示的数据,来源于弹框表格中选择的数据,该业务场景用过程式思维可描述为:

  1. 点击文本框
  2. 显示弹框,弹框内展示表格数据,可进行单选或多选
  3. 选择数据,点击确定或取消
  4. 弹框关闭,数据显示在文本框

二、抽取组件

当判断是否需要将某块业务场景抽成组件时,通常考虑的第一个要素是,是否会在多个地方共用,很显然,项目中存在多个子模块在多种场景中使用到该弹框组件的场景,为了减少代码的重复书写,将当前业务抽取成组件是再合适不过的。 vue3提供组件的全局注册与局部注册两种方式,同时也提供插件形式注册一到多个组件。组件注册方式可参阅官网,这里不再赘述。

2.1、组件定义

2.2、属性抽取

在业务需求章节,已经对组件的操作进行了描述,这里我们需要将父组件传递给子组件的属性先罗列出来,同时要定义组件自身与传递给父组件的数据。父组件的不同,导致传递给子组件的属性是不同的,我们在确定属性的时候,可以先把确切的与不确切的分类出来,这里只做个大概示范,不一一列出。

确切属性:父组件触发的时机决定了弹框的隐藏与显示,父组件的查询条件不同导致弹框列表数据DataSource不同,子组件点击不同操作,触发不同的方法handleOk/handleCancle通知父组件。

不确切属性:控制弹框宽度、滚动属性,是统一标准还是根据父组件来处理;列表rowKey,是根据接口规范统一定某个值,还是根据父组件传值处理;

组件自身数据:选择列表的数据信息,selectedKeys、selectedInfos,数据加载的loading标志。

简单来说,在开发组件初期,进行上面的定义是有效的,这个步骤不仅帮我们区分了props与data定义的范围,同时也让我们更加清晰组件有哪些依赖,做到心中有数。

2.3、组件实现

属性抽取之后,这里采用两种方式分别实现。

2.3.1 选项式API

这里首先介绍选项式API的实现方式,使用过vue2的通常比较熟悉,选项式API提供一种面向对象的方式来描述组件,同时将内置属性与方法暴露在公共实例this中,通过this可访问到到作用域中的所有属性与方法。使用vue的全局API defineComponent定义组件,同时将该组件export,根据抽取的属性和数据,定义组件props与data,有业务逻辑的话定义method,需要变更状态可定义状态选项watch和computed,涉及到声明周期操作,定义生命周期选项created、mounted等。

  • 定义props、data、watch、methods
// 基本定义
export default defineComponent({
    props: {
        // 弹框是否可见
        visible: {
           type: Boolean,
           default: false,
        },
        dataSource: {
            ....
        }
        ....
    },
    data() {
       return {
          dataSelectedKeys: [], // 当前选择的数据key
          dataSelectedInfos: [], // 当前选的数据info,
          localLoading: false, // 数据加载
          dataSource: []
       }
    },
    watch: { ... },
    methods: {
        /**
         * 取消操作
         */
        handleCancel() {
           this.$emit("handelCancel")
        },
        ...
    }
})

上面代码对组件定义进行了简单的示范,但我们发现组件并没有实际的dom返回,在选项式API中,利用render渲染函数,可以定义我们的组件内容。tsx的写法可参考文档。接上面代码,我们来完成组件的dom部分。

  • 定义render渲染函数
// tsx中渲染函数,直接return ant-design-vue组件内容,return中的()只是分隔符没有其他含义
render() {
    const { visible, title } = this.$props // 通过$props可获取props属性,也可以在组件中用this.visible取值
    return (
        <a-modal
           centered
           visible={visible}
           title={title}
           maskClosable={false}
           width="500px"
           onCancel={this.handleCancel}
           onOk={this.handleOk}
        >
            <a-table
               loading={this.localLoading}
               columns={columns}
               data-source={this.dataSource}
               pagination={false}
               scroll={scroll}
               rowKey={rowKey}
               rowSelection={{
                  selectedRowKeys: this.dataSelectedKeys,
                  type: selectType,
                  onChange: (selectedRowKeys, info) => {
                     this.dataSelectedKeys = selectedRowKeys
                     this.dataSelectedInfos = info
                  },
               }}
            >
            </a-table>
        </a-modal>
    )
}

组件已定义完整,但我们思考这样一个问题,弹框中包裹着table,table通常带有分页逻辑,如果每个父组件单独处理分页逻辑,该操作会繁琐重复,同时组件仅仅起到了接收数据的作用,价值并不高;如果分页逻辑,子组件可自行处理,父组件仅提供数据的实现方式,这样就可以减少大量的冗余操作。

组件处理逻辑可以表述为,父组件点击时,修改visible值,同时调用子组件中expose暴露的方法,该方法中,调用父组件的接口函数获取数据,获取到的数据在子组件中进行处理,同时分页逻辑也完全由子组件处理,父组件只需提供接口函数,其他都不需要关心了。

  • 定义获取数据方式和expose属性
props: {
    // 加载数据方法,是个异步函数,必传
    loadData: {
       type: Function,
       required: true,
    },
 }
 expose: ["getTableData"], // 暴露给父组件的调用函数
 watch() {
     pageSize(value) {
         this.pageSize = value 
         this.getTableData()
      }
 },
 methods: {
      getTableData(pagenation) {
          this.loading = true // 加载
          const params = Object.assign({}, pagenation) // 合并分页参数
          const result = this.loadData(params) // 调用父组件函数,传递分页参数
          if (isPromise(result)) {
              // 根据返回的promise,来对具体数据进行操作
              result.then((res) => {
                  // 无数据
                  ....
                  // 有数据可对dataSource赋值
                  this.dataSource = res.data
              }).catch(...).finally(...)
           }
      },
 }

到这里,组件定义已趋于完整,但表格列,在渲染的时候可能会有不同的展现形式,因此需要使用$slot来获取父组件的传入的插槽对象,将该对象渲染在组件中。

  • 定义slot处理
const slotNum: any = Object.keys(this.$slots)
<a-table>
    {{
       [slotNum]: (item: any) => this.$slots?.[slotNum]!(item),
    }}
</a-table>

2.3.2 组合式API实现

在非单文件组件中使用组合式API,需要通过setup来提供入口,setup函数,提供props与上下文属性,并通过return返回需要渲染的dom。 开发组件的步骤是一样的,在这里我们按照上文叙述的步骤来描述下如何利用组合式API编写代码。

  • 定义props与data

setup函数第一个参数为props,我们可以在外部定义props属性,然后利用setup参数来进行获取

export default defineComponent({
  props: {
        // 弹框是否可见
        visible: {
           type: Boolean,
           default: false,
        },
        dataSource: {
            ....
        }
        ....
    },
    // 通过props来获取传递属性
    setup(props) {
        const dataSelectedKeys = ref([]) // 定义data属性
        const handleCancel = () => {} // 定义方法
    }
 })
  • 定义render渲染函数

组合式API定义dom时,setup函数不能直接返回vnode,即不能像上文描述的一样,直接return,而是需要通过return函数返回vnode。注意,使用props.[属性]可以直接获取值,而用解构方式,将让props属性失去响应式。需要使用toRefs转成ref对象,才可保留响应式。

const { visible, title } = toRefs(props) // 不能直接解构props,需要使用toRefs转才能保留响应性

return () => 
   (
     <a-modal
        centered
        visible={props.visible} // 直接使用props访问,不转
        title={props.title}
        maskClosable={false}
        width="500px"
        onCancel={handleCancel}
        onOk={handleOk}
      >
         <a-table/>
      </a-modal>
 )
  • 定义获取数据方式和expose属性

数据的获取方式已在上文进行了解释,这里只介绍组合式API如何将组件方法,暴露给父组件使用。同样使用到了expose属性,该属性暴露在setup的第二个上下文参数中,需要在setup中暴露,同时要利用expose定义暴露方法。

setup(props, { expose }) {
    const getTableData = (pagenation) => {
        ...
    }
     expose({
        getTableData
     })
}
  • 定义触发父组件函数emit

emit同样暴露在setup的上下文参数中,不同的是emit需要使用emits定义在外部,然后在子组件中去触发。

  emits: ['handelCancel', 'handleOk'],
  setup(props, { emit }) {
      // 不传参的形式
      const handleCancel = () => {
          emit('handleCancle')
      }
      // 传递参数
      const handleOk = () => {
         emit("handleOk", dataSelectedKeys.value, dataSelectedInfos.value)
      }
  }
  • 定义slot处理

slot和expose、emit类似,都暴露在上下文content参数中,这里不再赘述,根据实际开发需要去处理即可。 至于watch、computed等可根据官网查阅,与SFC方式相同。

2.4、组件使用

父组件中使用很简单,由于当前组件已全局注册过,可在SFC单页面中直接使用。如果没有进行全局注册,vue3中提供的setup语法糖,可直接通过import引入后使用,不用再像vue2通过component属性定义了。

// 父组件的使用方式
<modal-table
   ref="duplicateKeyModal"
   :visible="duplicateKeyVisible"
   title="标题名称"
   :load-data="queryData"
   :columns="columns"
   select-type="checkbox"
   :scroll="{y: '280px'}"
   :initial-value="initKeyValue" // 选择的默认值
   row-key="columnName"
   @handelCancel="handleKeyCancel"
   @handleOk="handleKeyOk"
>
   <template #bodyCell="item">
      <!--序号链接-->
      <template v-if="item.column.dataIndex === 'serial'">
         {{ item.index 1 }}
      </template>
   </template>
</modal-table>
/**
 * 列表数据获取接口,这里就是提供给子组件的promise,必传属性
 */
const queryData = async() => {
     const params = { }
     const res = await queryTables(params)
     return res
}

2.5、两种方式的对比

在组件的实现方式上,两种都可以,用惯了vue2的,可能使用选项式API更得心应手,但对于大型项目,组合式API更胜一筹。如下图所示,选项式API在组织结构上冗余、逻辑与数据隔离,组合式API,通过setup的上下文参数暴露属性,放在一个公共的地方,对代码中使用到的数据追踪更容易。其次组合式API提供更好的类型推导,并且在生产包的代码压缩能力上更好。当然两种方式均可以实现组件的编写,选择合适自己项目的才是最重要的。

不过在实践过程中,如果是简单项目,个人认为选项式API更好用,代码组织较为清晰,逻辑简单不会存在跟踪难问题。但如果结合ts使用的话,还是组合式API更好。

学新通技术网

学新通技术网

2.6 最终效果

学新通技术网

学新通技术网

学新通技术网

学新通技术网

学新通技术网

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanffhhg
系列文章
更多 icon
同类精品
更多 icon
继续加载