使用 Ramda 以声明方式优先编写布尔值 -> 字符串映射器

Writing a Boolean -> string mapper with precedence in a declarative way using Ramda

如何以更 functional/declarative 的方式编写此代码?

type FieldType = "dropdown" | "text" | "file" | null;
const getFieldType = (field: {isDropdown?: boolean, isTextInput?: boolean, isFileModal?: boolean}) => {
// a field can have all 3 of the above boolean properties at once as "true"
//but some of them are more important than others - eg. isDropdown
//hence the if/else if below
if(field.isDropdown) {
  return 'dropdown';
} else if(field.isTextInput) {
  return 'text';
}
else if (field.isFileModal) {
  return 'file';
}
}

我的解决方案:

const getFieldType = (field) => {
  const mapConditionToType = [
    [field.isDropdown, 'dropdown'],
    [field.isTextInput, 'text'],
    [field.isFileModal, 'file']
  ]

  return mapConditionToType.find([condition, type] => condition)?.type ?? null;
}

如您所见,它根本不包含...任何 Ramda。我想知道是否可以使用函数式编程以更具声明性的方式编写它?

我建议利用一些声明性库,例如 Ramda

/**
 @typescript ```
   R.cond<Record<string, boolean>, FieldType>([ ... ]);
   ```
**/
const getFieldType = R.cond([
  
  [R.prop('isDropdown'), R.always('dropdown')],
  [R.prop('isTextInput'), R.always('text')],
  [R.prop('isFileModal'), R.always('file')],
  [R.T, R.always(null)],

]);

console.log(
  'it should return "dropdown" =>',
  getFieldType({ isDropdown: true }),
)

console.log(
  'it should return "file" =>',
  getFieldType({ isFileModal: true }),
)

console.log(
  'it should return "text" =>',
  getFieldType({ isTextInput: true }),
)

console.log(
  'it should return "null" =>',
  getFieldType({ isMultiSelect: true }),
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

如@walnut_salami 的评论所述,cond 是在这种情况下使用的正确运算符。您可以使用更高级的运算符来测试第一个参数的真实性,但由于要检查的属性 return 为真值,因此 prop 运算符应该足够了。

interface Field  {
  isDropdown?: boolean;
  isTextInput?: boolean;
  isFileModal?: boolean;
}

const getFieldType = cond<Field, FieldType>([
  [prop('isDropdown'), always('dropdown') ],
  [prop('isTextInput'), always('text') ],
  [prop('isFileModal'), always('file') ],
  [T, always(null)]
]);

console.log(getFieldType({ isDropdown: true })); // dropdown
console.log(getFieldType({ isTextInput: true })); // text
console.log(getFieldType({ isFileModal: true })); // file
console.log(getFieldType({ })); // null
console.log(getFieldType({ isTextInput: true, isDropdown: true })); // dropdown

由于您使用的是 TypesSript,因此您可以利用接受主题类型和 return 类型参数的 cond 的重载。通过这样做,你和重构 FieldFieldType 将抛出编译器错误,如果它们以某种方式破坏了运算符中使用的内容。

R.cond 适合您希望提供的对数组的结构。您可以使用 R.pipe 生成一个接受数组对的函数,然后根据 R.cond 生成一个新函数(来自 的测试用例):

const { pipe, map, evolve, prop, always, append, T, cond } = R

const createGetFieldType = pipe(
  map(evolve([prop, always])), // wrap each element in the pairs with the relevant function
  append([T, always(null)]), // add the default value
  cond // partially apply to cond
)

const getFieldType = createGetFieldType([
  ['isDropdown', 'dropdown'],
  ['isTextInput', 'text'],
  ['isFileModal', 'file']
])

console.log(getFieldType({ isDropdown: true })); // dropdown
console.log(getFieldType({ isTextInput: true })); // text
console.log(getFieldType({ isFileModal: true })); // file
console.log(getFieldType({ })); // null
console.log(getFieldType({ isTextInput: true, isDropdown: true })); // dropdown
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>