Рефакторинг операторов if с использованием Ramda

#javascript #typescript #ramda.js

#javascript #typescript #ramda.js

Вопрос:

Я новичок в использовании Ramda, поэтому, когда я выполнял функцию map (в моем приложении gatsby) для массива сообщений в блоге, в этой карте я использовал простые операторы if. Но я хочу сделать это правильно, используя Ramda до конца, но я нахожу множество вариантов подавляющими. Я определенно нашел и попробовал много примеров из StackOverflow и других источников и смог получить категории, но не весь пост в блоге на основе этих категорий. Поэтому, хотя я понимаю, что есть примеры, я изо всех сил пытаюсь правильно применить их в своем экземпляре. Есть какие-либо мнения о том, как я бы реорганизовал этот код?

     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
    )}
 

Любая помощь очень ценится.

Комментарии:

1. Если вы map замените обычным Array.forEach или просто for циклом — вы получите простой в обслуживании, чтении и 0 зависимостях код.

Ответ №1:

На мой взгляд, это неправильное использование 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. Мы могли бы начать изменять его, чтобы использовать более приятные map функции и filter функции Ramda, для использования toPairs вместо Object .fromEntries , возможно, для использования Ramda any вместо .some . Это может сделать это немного чище, и я бы рекомендовал вам попробовать, если вы находитесь в процессе изучения Ramda. Но это глазурь. Проблема заключается в упрощении структур данных и кода, который их использует.

Обновить

OP опубликовал рефакторинг на основе Ramda matchSlugs и запросил обратную связь.

Вот серия рефакторингов этой версии до версии без точек:

  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 принимает некоторые функции и возвращает функцию. При этом сохраняется описанное выше поведение.

  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
    )
  )
 

Для меня эта версия является наиболее читаемой из всех. Но о вкусах не спорят, и вы можете найти другую из этих или совершенно другую версию, наиболее читаемую.

Комментарии:

1. Это очень полезно с точки зрения рефакторинга кода в целом, и я успешно внедрил его в свой локальный. Я испытываю искушение оставить все как есть, но я думаю, что мои коллеги заинтересованы в максимально возможном использовании Ramda, поэтому я могу попытаться включить эти последние предложенные изменения в качестве следующего шага. Если у вас есть какие-либо конкретные рекомендации, я с радостью их приму. В любом случае, большое спасибо, это намного чище.

2. Я бы с осторожностью относился к «максимально возможному использованию Ramda». Ramda должна быть библиотекой утилит. Используйте его там, где это помогает, но не пытайтесь втиснуть в него все. Во что бы то ни стало, потратьте некоторое время на изучение того, что он может делать, и я надеюсь, что чем больше вы его используете, тем более полезным он кажется. Но есть много мест, где он не может предложить ничего полезного или где быстро расширяющийся ванильный JS уменьшил потребность в нем.

3. На случай, если кому-то интересно, вот как я реорганизовал функцию matchslugs для использования ramda (на самом деле мне больше ничего не нужно для моей текущей цели). @Scott Sauyet не стесняйтесь критиковать, как это делается. Ваши знания были очень полезны. const matchSlugs = (name) => (posts) => map(( { node }) => node, (filter( ({ node: { categories } }) => any( (({ slug }) => slug == name))(categories), posts ) )); [Примечание: не уверен, почему использование markdown для кода не сохраняет строки. Это потому, что это комментарий? Я постараюсь исправить это, чтобы сделать его более читаемым]

4. Да, комментарии не получают разрывов строк. Это боль, когда вы хотите включить фрагменты форматированного исходного кода. Я понимаю, почему они не делают этого на сайте вопросов и ответов. Предполагается, что это не дискуссионный форум, и комментарии предназначены в основном для получения разъяснений. Но это все еще боль.

5. Вау. Я просто хочу поблагодарить вас за этот очень тщательный набор рефакторингов. Очень полезно видеть прогресс, чтобы сделать осознанный выбор. Я действительно ценю время, которое вы потратили на это. БОЖЕ!

Ответ №2:

Вы можете уменьшить узлы до объекта узлов с помощью пуль, а затем выбрать запрошенный пуль в указанном порядке:

 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>