更多文章
前言
项目开发是小伙伴们的家常便饭,追旅也参与了很多项目的开发,抛去大厂基本都是在做傻瓜式
式开发,追旅大多数时候也是在着傻瓜式
的开发,例如用umijs
、vue-cli
或者react
的各种脚手架(我们不谈这些脚手架的优劣),参考脚手架的api基本上也就可以快速启动一个项目,相当的方便,这让开发者可以把更多的精力投入业务开发中去,所以想要在这块有所突破要么webpack
自行撸一套脚手架,要么想尤大大那样创造新大陆vite
,追旅这里分享一下更实际的业务开发,如何更好的落地组件化模块化
UI层
ui层组件的抽离,相信大家都做过很多了
基础组件
element-ui
、antd-design
等组件库中你看得到的均属于基础组件,组件库不包含的基础组件也是基础组件,只是要自己去封装,对于绝大多数场景来说这些组件已经是最小单位了,但是在实际项目中基础组件也是需要进行业务封装的,原因如下:
- 基础组件在书写业务时也会导致大量的重复性代码和功能
- 基础组件在组装成业务组件时也有可能因为不满足需求需要拓展
- ui层的操作不变可以通过控制数据层影响ui层
以table
和form
为demo
(后续会使用到):
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>
业务组件
一次性的业务组件可以一次性封装,但绝大多数场景不是这样的,还是以form
和table
为例,不单单是只有列表查询的场景会使用到form
和table
,新增
、添加
等场景会用到form
,对于其他场景亦会用到table
,假设我们将上述两个基础组件放在一起没有做抽离,我也只有列表查询一个固定场景,而对于其他表单提交场景或者列表展示场景则需要额外的再去写一个form
或者table
组件,这样导致的问题是:
- 场景单一、无法服用
- 业务组件中的基础组件无法复用,利用度不高
- 对于样式统一度较高的平台不利于统一
所以结论就是:
业务组件 += 基础组件
业务组件必定是基础组件组装的(除非你能保证你永远不在用着业务组件中的基础组件),最终查询表单场景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
依然是table
和form
的demo
:
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优势尽显
高内聚、低耦合
高内聚、低耦合是个经久不衰的话题,低耦合就是拆分的过程,高内聚就是组装的过程,一辆可以跑起来的车是由无数个零件组装起来的,如果可以把组装和拆分做好,项目的可维护度、可读性就变得非常的高,各大框架核心依然是组件化、模块化,当开发者做的足够好时tslint
、eslint
等辅助性工具也只是锦上添花而已,不知道你是否见到过2000行的ts代码?组件化、模块化是种思想,这种思想深入人心的同时,是否可以将这种思想落地的更好
总结
封装是门艺术
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!