用 Ramda 将数组转字典

180 阅读2分钟

想出个 R.prop() 的新用法

前情

Vue 项目,组件库用 ant-design-vue@1.7.8。

起因

有个设备管理页面,有表单和表格,表单用于新增或编辑数据,表格用于展示数据。

表单中有一项是设备类型,是下拉选择,用 a-select 就需要一个数组,数组元素要含属性 valuelabel,表格其中一列要显示设备类型。

项目中有存放相关数组,typeOptions 当然就要在这两处地方派上用场了。

最初做法是用 R.find()typeOptions 中找和数据 type 值相同的那一项,再读 label

export const typeOptions = [
  { value: 'pc', label: '电脑' },
  { value: 'pad', label: '平板' },
  { value: 'phone', label: '手机' },
];
<template>
  <a-table :columns="columns" :data-source="tableData" />
</template>

<script>
import * as R from 'ramda';
import { typeOptions } from '@/const/device.js';

export default {
  data() {
    return {
      columns: [
        {
          title: '设备名称',
          dataIndex: 'name',
        },
        {
          title: '设备类型',
          dataIndex: 'type',
          customRender: text => R.find(R.propEq(text, 'value'), typeOptions).label,
         },
      ],
      tableData: [],
    };
  };
};
</script>

思考

项目中有很多表格,有的列要展示字段值对应的文字,都需要用到 xxxOptions 数组,总用 find() 找到对应的项再读 label 的写法不太美观。

(为啥不美观?个人喜好)

如果能像对象那样,直接读 value,得到 label 就好了。

开工

  1. 将 xxxOptions 数组指定属性转对象

    export const toDic = R.curry(
      (ps, xs) => R.compose( R.fromPairs, R.map(R.props(ps)) )(xs)
    );
    
    /** 常见的 xxxOptions 数组属性含 `value`, `label`,也可以指定其他属性为键和值 */
    export const toDicValueLabel = toDic(['value', 'label']);
    

    比如,设备数据 type 值是 pc,传入对象后得到 电脑

    toDicValueLabel(typeOptions); // { pc: '电脑', 'pad': '平板', 'phone': '手机' }
    
  2. 改造 customRender

    - customRender: text => R.find(R.propEq(text, 'value'), typeOptions).label,
    + customRender: R.prop(R.__, toDicValueLabel(typeOptions)),
    

    label 没取到需要备用值,可以用 R.propOr

    - customRender: text => R.find(R.propEq(text, 'value'), typeOptions).label || '--',
    + customRender: R.propOr('--', R.__, toDicValueLabel(typeOptions)),
    

延伸

有时需要判断数据的某个属性值,是否等于期望值。

以前的做法是,读数据的属性值,再判断是否等于期望值,期望值每次参照 xxxOptions 后写死。

比如有个设备申请管理页面:

const auditStatusOptions = [
  { value: 0, label: '待提交' },
  { value: 1, label: '待审核' },
  { value: 2, label: '审核通过' },
  { value: 3, label: '已驳回' },
];

/** 是否已通过审核 */
const isPassed = record => 2 == record.auditStatus;

用上 toDicValueLabel() 之后就在想,期望值如果写成 label 那样的文字,可读性就比用 value 那种数字或单词更强,于是也改了此类函数:

  1. 读到属性值对应的 label

    import * as R from 'ramda';
    import { toDicValueLabel } from '@/util/index.js';
    import { auditStatusOptions } from '@/const/device.js';
    
    const isPassed = R.compose(
      /* 判断函数 */
      R.prop(R.__, toDicValueLabel(auditStatusOptions)),
      R.prop('auditStatus'),
    );
    
  2. 传入函数判断 label 是否符合条件

      const isPassed = R.compose(
    -   /* 判断函数 */
    +   R.equals('审核通过'),
        R.prop(R.__, toDicValueLabel(auditStatusOptions)),
        R.prop('auditStatus'),
      );
    

可以根据需求传入函数,若想判断属性值是否属于某一类值可以这样:

const isMobileDevice = R.compose(
  R.includes(R.__, ['平板', '手机']),
  R.prop(R.__, toDicValueLabel(typeOptions)),
  R.prop('type'),
);