使用 Ramda 以声明性方式编写具有优先级的 Boolean -> 字符串映射器
Posted
技术标签:
【中文标题】使用 Ramda 以声明性方式编写具有优先级的 Boolean -> 字符串映射器【英文标题】:Writing a Boolean -> string mapper with precedence in a declarative way using Ramda 【发布时间】:2021-10-30 01:43:42 【问题描述】:如何以更具功能性/声明性的方式编写此代码?
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。我想知道是否可以使用函数式编程以更具声明性的方式编写它?
【问题讨论】:
Field
可以同时是isDropdown: true
和isTextInput: true
吗?
是的。请参阅我在第一个代码块中的评论“一个字段可以包含所有 3 个 ...”。但你是对的,我没有提到它们都可以是真的:P
但find
将返回第一次出现,而不是全部。如果所有三个道具都为真,那么预期的行为是什么?
预期行为是返回第一个匹配元素。 “isDropdown”比“isTextInput”等重要。
嗯,R.cond 看起来不错
【参考方案1】:
我建议利用一些声明性库,例如 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>
【讨论】:
【参考方案2】:正如@walnut_salami 的评论中所述,cond 是在这种情况下使用的正确运算符。您可以使用更高级的运算符来测试第一个参数中的真实性,但由于被检查的属性返回真实值,所以 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,因此您可以利用 cond 的重载,它接受主题类型和返回类型的参数。通过这样做,您和重构为 Field 和 FieldType 将引发编译器错误,如果它们以某种方式破坏了运算符中正在使用的内容。
【讨论】:
【参考方案3】:R.cond
适合您希望提供的对数组的结构。您可以使用R.pipe
生成一个接受对数组的函数,然后根据R.cond
生成一个新函数(来自Daniel Gimenez's answer 的测试用例):
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>
【讨论】:
以上是关于使用 Ramda 以声明性方式编写具有优先级的 Boolean -> 字符串映射器的主要内容,如果未能解决你的问题,请参考以下文章