业务中条件渲染的另一种玩法:从 ProTable 到组合函数
摘要: 将查询条件和表格配置集中到一个 JSON 数据中,用函数组合处理筛选逻辑,比手写 if/else 更清晰可维护。
背景:ProTable 的配置思路
ProTable 是 Ant Design 的扩展组件库,集合了查询、表格和分页功能。它将 Search 和 Table 的数据维护在一份 JSON 中,用不同参数驱动 UI。
基本配置:

从 ProTable 的配置可以看出,查询表单和表格属性栏全部集中到一个 JSON 对象中管理。通过 hideInSearch、hideInForm、hideInTable 三个字段来区分该配置项所在的模块渲染。例如,以下 Column 定义表示三个模块全部不渲染(类似权限控制):
javascript
[
{
title: 'A',
dataIndex: 'A',
hideInSearch: true,
hideInForm: true,
hideInTable: true
}
]用 Array.prototype.filter 就可以轻易完成筛选操作。
组合函数 compose
组合函数可以仿写 Redux 的实现,不超过十行,但功能非常强大:
javascript
function compose (...fn) {
if (fn.length === 0) {
return args => args
}
if (fn.length === 1) {
return fn[0]
}
return fn.reduce((f, g) => (...args) => f(g(args)))
}运用 compose 的方法很简单。例如:
- 将 Enum 类型对象转换为数组
- 做一次权限控制筛选
- 根据不同字段动态添加属性
- 根据业务逻辑无限扩展
日常业务中经常遇到复杂逻辑全部写在一个函数内部的情况,随着业务升级,控制逻辑的代码越来越多。结论:必须将复杂业务逻辑一点一点拆分出去,业务环节状态流转越多,代码越好控制。
示例:Enum 转数组 + 权限 + 扩展属性
javascript
// 将 Enum 转换为 Array
// @params {obj} Object
// {
// 1: { label: '标签1', value: '0' },
// 2: { label: '标签2', value: '2' }
// }
function enumConvertToArray (obj = {}) {
try {
if (obj === null || obj === undefined || typeof obj !== 'object') return obj
return Object.entries(obj).map(([key, value]) => ({ [value.label]: key, ...value }))
} catch (error) {
console.error(error)
}
}
const ops = {
getProto: item => item.value === 2
? ({ ...item, name: 'yes' })
: ({ ...item, name: 'no' })
}
function filterColumnsBy (arr = [], cb) {
if (!Array.isArray(arr)) return arr
return [arr.filter(item => cb ? cb(item) : (item.value > 1)), ops]
}
function appendProtoBy (args) {
const [arr, ops] = args
if (!Array.isArray(arr)) return arr
return arr.map(item => ({ ...item, extend: ops.getProto(item) }))
}
// 组合三个条件分支
var composeColumns = compose(appendProtoBy, filterColumnsBy, enumConvertToArray)
var obj = {
1: { label: 'aaa', value: 1 },
2: { label: 'bbb', value: 2 },
3: { label: 'ccc', value: 3 }
}
var columns = composeColumns(obj)
console.log('current is columns: ', columns)如果任意环节需要改变逻辑,直接找到对应的逻辑函数修改即可。
对比:传统方式 vs 组合方式
看过一段管理后台的代码,差点看吐了:
javascript
const commonQuery = [
{ label: 'AAA', field: 'AAA' },
{ label: 'BBB', field: 'BBB' }
]
const serviceQuery = [{ label: 'service', field: 'service' }]
const businessQuery = [{ label: 'business', field: 'business' }]
const composeQuery = []
if (type === 'service') {
composeQuery = commonQuery.concat(serviceQuery)
} else if (type === 'business') {
composeQuery = commonQuery.concat(businessQuery)
}优化后:
javascript
const agreevQuery = [
{ label: 'AAA', field: 'AAA', auth: ['service'] },
{ label: 'BBB', field: 'BBB', auth: ['business'] },
{ label: 'CCC', field: 'CCC', auth: ['type'] }
]
const AUTH_TYPE = 'business'
const filterColumns = (origin = []) =>
origin.filter(item => item.auth.indexOf(AUTH_TYPE) > -1)如果需要控制排序,加一个 index 字段:
javascript
const agreevQuery = [
{ label: 'AAA', field: 'AAA', auth: ['service'], index: 1 },
{ label: 'BBB', field: 'BBB', auth: ['business'], index: 3 },
{ label: 'CCC', field: 'CCC', auth: ['type'], index: 2 }
]
const sortColumns = (origin = []) => origin.sort((a, b) => a.index - b.index)
const filterIndexColumns = (origin = []) =>
origin.map(item => ({
...item,
index: typeof item.index === 'function' ? item.index(item) : item.index
}))
const filterColumns = (origin = []) =>
origin.filter(item => item.auth.indexOf(AUTH_TYPE) > -1)
const composeColumns = compose(sortColumns, filterIndexColumns, filterColumns)
const columns = composeColumns(agreevQuery)总结
将复杂的复合式逻辑一条一条抽离,然后一个一个组合,数据流转就变得清晰透明,可以快速排查错误位置。设计模型时,将变动的模块或方法留出入口,允许外部传入函数组合内部方法去实现需求。