我将Ramda.js用于选择器功能,以访问Redux存储中的数据。我想要的是将选择器定义为不引用选择器所作用的state
的函数,例如:
const getUserName = path(['user', 'name']);
const name = getUserName({
user: {
name: 'Some Name'
}
});
这对于简单的选择器来说很容易,但是对于组合选择器有时会成为问题。
这是一个示例,其中一些items
需要解析,并由其id
在对象上引用:
const getItemById = id => state => path(['items', id], state);
const getConnectedItemIds = obj => path(['items'], obj);
const getItemsFromObj = obj => state => {
const ids = getConnectedItemIds(obj);
return ids.map(id => getItemById(id)(state));
};
第一个函数可以很容易地表达而无需引用state
,第二个函数可以不包含obj
,我认为这被称为无点样式。但是,如何在没有state
的情况下编写第三个函数呢?
我正在寻找如何使用Ramda重写第三个函数,以及与此有关的规则和过程,例如(不知道其正确性):
所有组合函数都必须以state
作为其最后一个参数,以便能够将其从最终组合中提取出来。
答案 0 :(得分:4)
这里已经有很多好的建议。可能最重要的建议是仅在提高可读性时才使用无点建议,而不是将其作为目标。
我的回答的确使用了您的主要功能和一个助手的无点版本,但是我认为它会影响可读性。
const getItemById = id => path(['items', id]);
const getConnectedItemIds = prop ('items');
const getItemsFromObj = pipe (
getConnectedItemIds,
map (getItemById),
juxt
)
const obj = {foo: 42, items: ['8', '17']}
const state = {bar: 1, items: {'6': 'a', '8': 'b', '14': 'c', '17': 'd', '25': 'e'}}
console .log (
getItemsFromObj (obj) (state)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {juxt, map, path, pipe, prop} = R </script>
这里的重要函数是juxt
,它将一个函数数组应用于相同的值,并返回结果数组。
这里,getItemById
通过删除state
点而被简化,但是据我所知,使此无点仅以牺牲可读性为代价。其他人对此提出了建议,所有这些都很好,但是正如他们指出的那样,没有一个比上面的可读性高。我的版本看起来像这样:
const getItemById = compose (path, flip (append) (['items']));
// or pipe (flip (append) (['items']), path);
我认为这并不比其他答案中的建议更好或更糟,但它们都不比它容易读
const getItemById = id => path(['items', id]);
最后,如果这些辅助功能未在其他地方使用,我认为实际上可以提高内联它们的可读性:
const getItemsFromObj = pipe (
prop ('items'),
map (id => path(['items', id])),
juxt
)
注意,尽管我在这里没有使用它,但我确实很喜欢customcommander对propOr([], 'items')
的{{1}}的建议。它彻底清除了潜在的故障点。
答案 1 :(得分:2)
我认为您仍然可以毫无意义地编写它, 但是,可读性受到了影响。 希望对您有所帮助:)
/**
* @param {string} id
* @param {Object<string, *>} state
**/
const getItemById = R.useWith(R.prop, [
R.identity,
R.prop('items'),
]);
const getItemsFromObj = R.useWith(R.flip(R.map), [
R.pipe(R.prop('items'), R.map(getItemById)),
R.applyTo,
]);
const items = {
1: { id: 1, title: 'Hello World' },
2: { id: 2, title: 'Hello Galaxy' },
3: { id: 3, title: 'Hello Universe' },
};
const state = { items };
// this should only take 'world' and 'universe';
console.log(
'result',
getItemsFromObj({ items: [1, 3] }, state),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
obj => path(['items'], obj);
等于path(['items']);
答案 2 :(得分:2)
我喜欢自由风格。我认为这迫使您对功能设计进行三思,这总是一件好事。但是,绝不要成为目标。仅在有意义时使用它。
此功能已经足够好了
const getItemById = id => state => path(['items', id], state);
我唯一的建议是使用curry
:
const getItemById = curry((id, state) => path(['items', id], state));
虽然您可以将其转换为无点样式,但是我认为这实际上不值得:
const getItemById = useWith(path, [pair('items'), identity]);
让我们回到您的问题。
我们具有以下两个功能:
const getItemById = curry((id, state) => path(['items', id], state));
const getConnectedItemIds = propOr([], 'items');
您可以这样设计getItemsFromObj
无点样式:
const getItemsFromObj = useWith(flip(map), [getConnectedItemIds, flip(getItemById)]);
或者您也可以简单地这样做:
const getItemsFromObj = curry((obj, state) => {
const ids = getConnectedItemIds(obj);
const idFn = flip(getItemById)(state);
return map(idFn, ids);
});
我会推荐哪个版本?我不知道;这里有一些想法可供考虑:
我建议的一件事是,您应该熟悉 Hindley-Milner类型系统。肯定是时间投入了。
不要让任何人告诉您,如果您没有按照功能进行编程,那么您将不能正确地进行函数式编程。
答案 3 :(得分:1)
可以使它短而无点,但是我不确定它是否比使用变量的a函数更具可读性。
const getItemsFromObj = R.useWith(R.props, [R.prop('items'), R.prop('items')])
我在这里不使用您的getItemById
,因为这种情况最适合R.props
。如果您确实要使用两个原始功能,则可以这样编写。
const getItemsFromObj = R.useWith(
R.flip(R.map), [getConnectedItemIds, R.flip(R.uncurryN(2, getItemById))]
)
需要翻转和毫不费力地将getItemById
函数参数从id -> state -> value
转换为state -> id -> value