「Vue3实践」- vue3两种API方式+TSX实现弹框表格组件
为了清晰的描述,如何实现一个弹框表格组件,我将按照组件的开发过程来讲解,即需求理解/拆分->组件定义->组件实现->组件应用。
一、业务需求
项目中涉及多个业务子模块,不同的子模块中都有对表单操作,其中有个表单项,是一个input文本框,该文本框与一般的文本框不同,不用于手动输入数据,而仅仅用于展示数据。展示的数据,来源于弹框表格中选择的数据,该业务场景用过程式思维可描述为:
- 点击文本框
- 显示弹框,弹框内展示表格数据,可进行单选或多选
- 选择数据,点击确定或取消
- 弹框关闭,数据显示在文本框
二、抽取组件
当判断是否需要将某块业务场景抽成组件时,通常考虑的第一个要素是,是否会在多个地方共用,很显然,项目中存在多个子模块在多种场景中使用到该弹框组件的场景,为了减少代码的重复书写,将当前业务抽取成组件是再合适不过的。 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
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24