使用 Ramda 重构 if 语句

Refactoring if statements using Ramda

我刚开始使用 Ramda,所以当我在博客数组 post 上执行映射函数(在我的 gatsby 应用程序中)时,在该映射中我使用了简单的 if 语句。但我想以正确的方式做到这一点,一直使用 Ramda,但我发现它有很多选择。我肯定已经从 Whosebug 和其他地方找到并尝试了许多示例,并且已经能够获取类别但不是基于这些类别的整个博客 post。因此,虽然我意识到那里有示例,但我正在努力在我的实例中正确应用它们。关于我将如何重构这段代码有什么意见吗?

    const featured_post = [];
    const hrt_category = [];
    const work_category = [];
    const prep_category = [];
    const ed_category = [];

    {map(
        ({ node }) => {

            if (node.categories.some(e => e.slug === 'featured')) {
                featured_post.push(node);
            }
            if (node.categories.some(e => e.slug === 'hormones')) {
                hrt_category.push(node);
            }
            if (node.categories.some(e => e.slug === 'estrogen')) {
                work_category.push(node);
            }
            if (node.categories.some(e => e.slug === 'prep')) {
                prep_category.push(node);
            }
            if (node.categories.some(e => e.slug === 'ed')) {
                ed_category.push(node);
            }

        },posts
    )}

非常感谢任何帮助。

在我看来,这是对 Ramda 的滥用(免责声明:我是 Ramda 的创始人。)

Ramda 就是处理不可变数据。尽管 Ramda 函数的内部可能会改变局部变量,但它们绝不会改变任何其他东西。

使用 map 来创建诸如推送到全局变量之类的副作用对于 Ramda 来说绝对是不匹配的。

除此之外,我不喜欢这种重复的代码。我的看法会完全不同。我不会使用所有这些全球帖子集合。

这是一种可能性:

const groupPosts = (names, posts) =>
  Object .fromEntries (names .map (name => [
    name, 
    posts .filter (
      ({node: {categories}}) => categories .some (({slug}) => slug == name)
    ) .map (({node}) => node)
  ]))

const posts = [{node: {id: 1, categories: [{slug: 'prep'}, {slug: 'ed'}]}}, {node: {id: 2, categories: [{slug: 'estrogen'}]}}, {node: {id: 3, categories: [{slug: 'ed'}]}}, {node: {id: 4, categories: [{slug: 'hormones'}, {slug: 'prep'}]}}, {node: {id: 5, categories: [{slug: 'featured'}]}}, {node: {id: 6, categories: [{slug: 'prep'}]}}, {node: {id: 7, categories: [{slug: 'estroogen'}]}},]

console .log (JSON .stringify (
  groupPosts (['featured', 'hormones', 'estrogen', 'prep', 'ed'], posts)
, null, 2))
.as-console-wrapper {max-height: 100% !important; top: 0}

但是如果你想要那些单独的子集合,我们可以从中提取匹配单个子集合的函数:

const matchSlugs = (name) => (posts) =>
  posts .filter (
    ({node: {categories}}) => categories .some (({slug}) => slug == name)
  ) .map (({node}) => node)

我们会像 const featured_posts = matchSlugs ('featured') (posts) 一样使用它,我们的原始函数将是一个简单的注释:

const sortPosts = (names) => (posts) =>
  Object .fromEntries (names .map (name => [name, matchSlugs (name) (posts)]))

请注意,这还没有在任何地方使用 Ramda。我们可以开始更改它以使用 Ramda 更好的 mapfilter 函数,使用 toPairs 代替 Object .fromEntries,可能使用 Ramda 的 any 代替.some。它可能会使它更清晰一些,如果您正在学习 Ramda,我建议您尝试一下。但这只是锦上添花。关键在于简化数据结构和使用它们的代码。

更新

OP 发布了 matchSlugs 的基于 Ramda 的重构并寻求反馈。

以下是将该版本重构为完全无点版本的一系列操作:

  1. OP的解决方案(由于注释不允许我们显示布局,因此使用我自己的布局):

    const matchSlugs = (name) => (posts) =>
      map (
        ({node}) => node, 
        filter(
          ({node: {categories}}) => any ((({slug}) => slug == name)) (categories),
          posts 
        )
      );
    
  2. 拉出posts参数后:

    const matchSlugs2 = (name) =>
      pipe (
        filter(({node: {categories}}) => any ((({slug}) => slug == name)) (categories)),
        map (prop ('node'))
      )
    

    此版本将现有节点的过滤与结果的最终映射分开,并且通过将这两个步骤放入 pipe 调用中,允许我们删除第二个参数。请注意 pipe 需要一些函数,而 returns 需要一个函数。这保留了上述行为。

  3. 清理filter中的解构参数后:

    const matchSlugs3 = (name) =>
      pipe (
        filter (
          pipe (
            path (['node', 'categories']),
            any (({slug}) => slug == name)
          )
        ),
        map (prop ('node'))
      )
    

    这里我们用path (['node', 'categories'])来代替({node: {categories}}参数。这涉及到另一个 pipe 调用,我希望稍后清理它。

  4. propEq

    替换匿名函数后
    const matchSlugs4 = (name) =>
      pipe (
        filter (pipe (
          path (['node', 'categories']),
          any (propEq ('slug', name))
        )),
        map (prop ('node'))
      )
    

    这只是一个小修复,但 propEq 对我来说比 lambda 函数更清晰。它还将允许我们进行下一次重构。

  5. 重构移除name参数后:

    const matchSlugs5 = pipe (
      propEq ('slug'),
      any,
      flip (o) (path (['node', 'categories'])),
      filter,
      o (map (prop ('node')))
    )
    

    这是最大的一步。我们使用 o 的两个实例,Ramda 的柯里化二进制版本 compose,将其变成一个无点函数。这对于使函数无点化非常有用,但感觉相当晦涩。 (o 这个名字是为了提醒我们数学组合符号,。)第一次我们需要 flip o,因为 Ramda 没有 pipe 风格的 o.

对于某些Ramda用户来说,这将是最终目标,实现功能的无积分版本。有时这非常可读,但我个人认为这最后一步 降低了 可读性。我可能会回到早期的有针对性的版本,或者更好的是,回到受此版本启发的版本,但要点已恢复:

const matchSlugs6 = (name) => (posts) =>
  map (
    prop('node'), 
    filter (
      compose (any (propEq ('slug', name)), path (['node', 'categories'])),
      posts
    )
  )

对我来说,这个版本是最易读的版本。但是口味不同,您可能会发现其中的另一个版本或完全不同的版本最具可读性。

您可以通过 slug 将节点缩减为节点的对象,然后按规定的顺序选择请求的 slug:

const { mergeWith, concat, reduce, pick } = R

const mergeConcat = mergeWith(concat)

const groupPosts = (names, posts) => pick( // pick the names you want in the correct order
  names,
  reduce((p, { node }) => mergeConcat(p, // merge with the general categories
    reduce((c, { slug }) => // create an object of node by categories
      ({ ...c, [slug]: [node] }), {}, node.categories)
  ), {}, posts)
)

const posts = [{node: {id: 1, categories: [{slug: 'prep'}, {slug: 'ed'}]}}, {node: {id: 2, categories: [{slug: 'estrogen'}]}}, {node: {id: 3, categories: [{slug: 'ed'}]}}, {node: {id: 4, categories: [{slug: 'hormones'}, {slug: 'prep'}]}}, {node: {id: 5, categories: [{slug: 'featured'}]}}, {node: {id: 6, categories: [{slug: 'prep'}]}}, {node: {id: 7, categories: [{slug: 'estroogen'}]}},]

console.log (JSON.stringify (
  groupPosts (['featured', 'hormones', 'estrogen', 'prep', 'ed'], posts)
, null, 2))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>