使用 ramda 通过变量获取 ascending/descending

Get ascending/descending by a variable with ramda

我有一个要按名称排序的用户列表。该列表如下所示:

const data = [
  { id: 2, name: 'Asterios' },
  { id: 1, name: 'Alex' },
  { id: 4, name: 'Tim' },
  { id: 3, name: 'Sadie' },
]

我实现了一个相对简单的选择器,它将根据一些 属性.

对用户列表进行排序
const getUserList = createSelector(
  getUsers,
  getSortBy,
  getSortOrder,
  (users, sortBy, sortOrder) => R.sortWith([
    R.ascend(R.prop(sortBy)),
  ])(users),
);

但是,我想使用变量 sortOrder,它可以是 'ASC''DESC',并应用该排序顺序。

我试过这样的事情:

const sortDirection = R.ifElse(R.equals('DESC'), descend, ascend);

sortWith([ compose(sortDirection(sortOrder), prop('name')), ])

是否有通过变量应用此 ascending/descending 排序逻辑的好方法?

这不使用 Ramda,但希望它能让您了解如何开始解决问题

const ascComparator = (a, b) =>
  a < b
    ? -1
    : a > b
      ? 1
      : 0

const descComparator = (a, b) =>
  ascComparator (b, a)

const data =
  [ { id: 2, name: 'Asterios' }
  , { id: 1, name: 'Alex' }
  , { id: 4, name: 'Tim' }
  , { id: 3, name: 'Sadie' }
  ]

data.sort ((a,b) => ascComparator (a.name, b.name))

console.log (data)
// Alex, Asterios, Sadie, Tim

data.sort ((a,b) => descComparator (a.name, b.name))
  
console.log (data)
// Tim, Sadie, Asterios, Alex

上面的程序演示了 contramap

的一个漂亮用例

const contramap = (f, g) =>
  (a, b) =>
    f (g (a), g (b))

const prop = k => o =>
  o [k]

const ascComparator = (a, b) =>
  a < b
    ? -1
    : a > b
      ? 1
      : 0

const descComparator = (a, b) =>
  ascComparator (b, a)

const data =
  [ { id: 2, name: 'Asterios' }
  , { id: 1, name: 'Alex' }
  , { id: 4, name: 'Tim' }
  , { id: 3, name: 'Sadie' }
  ]

data.sort (contramap (ascComparator, prop ('name')))

console.log (data)
// Alex, Asterios, Sadie, Tim

data.sort (contramap (descComparator, prop ('name')))
  
console.log (data)
// Tim, Sadie, Asterios, Alex

localeCompare 也有效

const ascComparator = (a, b) =>
  a.localeCompare (b)

const descComparator = (a, b) =>
  ascComparator (b, a)

好的,你这里有几个小问题。不过,调试一个大问题通常比调试几个小问题更容易。

正确使用ifElse

第一个是ifElse的使用。这需要三个函数作为参数,returns 一个新函数,根据用你的参数调用第一个函数的结果,调用第二个或第三个函数争论。请注意,它没有 return 功能;它叫它。有几种方法可以解决这个问题。您可以使用 always:

包装这些函数
const sortDirection = R.ifElse(R.equals('DESC'), always(descend), always(ascend))

但我认为放弃无积分并使用

更简单
const sortDirection = dir => (dir === 'DESC' ? descend : ascend)

了解compose

其次,您向 ascenddescend 传递了一个稍微错误的值。虽然这不是完全的实现,但可以将 ascend 视为类似

的东西
const ascend = curry(fn, a, b) => fn(a) < fn(b) ? -1 : fn(a) > fn(b) ? 1 : 0);

请注意,如果您将一元函数传递给它,例如 fn = prop('name'),您将返回 (a, b) => fn(a) < fn(b) ? -1 : fn(a) > fn(b) ? 1 : 0,这是一个二元比较器函数。 sortWith 接受比较器列表。这样就好了。

但是如果你的sortOrder'ASC',那么这个

sortWith([ compose(sortDirection(sortOrder), prop('name')), ])

变成

sortWith([ compose(ascend, prop('name')) ])

相当于

sortWith([ x => ascend(prop('name')(x)) ])

而且传递给排序器的函数不是一个合适的比较器。它是一个一元函数。问题是 prop('name') 是一个一元函数,所以 compose 并没有达到您的预期。

如果稍微重新排列,您可以获得正确的行为:

sortWith([ compose(sortDirection(sortOrder), prop)('name'), ], )

这将首先使用 ASC 转换为

sortWith([ compose(ascend, prop)('name'), ], )

因此

sortWith([ (x => ascend(prop(x)))('name'), ], )

sortWith([ ascend(prop('name')), ], )

正如我们所见,ascend(fn) 是一个二进制比较器。

放在一起

所以解决问题的一种方法是

const sortDirection = R.ifElse(R.equals('DESC'), always(descend), always(ascend))
const sortOrder = 'DESC'
sortWith([ compose(sortDirection(sortOrder), prop)('name'), ], data)
//=> [{.. Tim}, {.. Sadie}, {.. Asterios}, {.. Alex}]

当然,如果 sortOrder = 'ASC',那么

sortWith([ compose(sortDirection(sortOrder), prop)('name'), ], data)
//=> [{.. Alex}, {.. Asterios}, {.. Sadie}, {.. Tim}]

替代方法

关于上面的内容,有两点我仍然非常不喜欢:

  1. 它使用自由变量sortOrder。我更希望这些变量是我函数的参数。

  2. 它使用sortWith而不是sortsortWith 组合 比较器函数的一种方式。因为我们只有一个,所以sort更简单。

下面是我可能会如何编写它来解决这些问题:

const {ascend, descend, prop, sort} = R
const data = [{ id: 2, name: 'Asterios' }, { id: 1, name: 'Alex' }, { id: 4, name: 'Tim' }, { id: 3, name: 'Sadie' }]

const sorter = dir => (dir === 'DESC' ? descend : ascend)(prop('name'))

console.log(sort(sorter('DESC'), data))
//=> [{.. Tim}, {.. Sadie}, {.. Asterios}, {.. Alex}]
console.log(sort(sorter('ASC'), data))
//=> [{.. Alex}, {.. Asterios}, {.. Sadie}, {.. Tim}]
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>