#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
и запросил обратную связь.
Вот серия рефакторингов этой версии до версии без точек:
- Решение OP (с моим собственным макетом, поскольку комментарии не позволяют нам отображать макет):
const matchSlugs = (name) => (posts) => map ( ({node}) => node, filter( ({node: {categories}}) => any ((({slug}) => slug == name)) (categories), posts ) );
- После извлечения
posts
параметра:const matchSlugs2 = (name) => pipe ( filter(({node: {categories}}) => any ((({slug}) => slug == name)) (categories)), map (prop ('node')) )
Эта версия отделяет фильтрацию существующих узлов от окончательного сопоставления результатов и, помещая эти два шага в
pipe
вызов, позволяет нам удалить второй аргумент. Обратите внимание, чтоpipe
принимает некоторые функции и возвращает функцию. При этом сохраняется описанное выше поведение. - После очистки деструктурированного параметра в
filter
:const matchSlugs3 = (name) => pipe ( filter ( pipe ( path (['node', 'categories']), any (({slug}) => slug == name) ) ), map (prop ('node')) )
Здесь мы используем
path (['node', 'categories'])
для замены({node: {categories}}
параметра. Это включает в себя ещеpipe
один вызов, который я надеюсь очистить позже. - После замены анонимной функции на
propEq
const matchSlugs4 = (name) => pipe ( filter (pipe ( path (['node', 'categories']), any (propEq ('slug', name)) )), map (prop ('node')) )
Это всего лишь незначительное исправление, но
propEq
для меня оно читается более чисто, чем функция lambda. Это также позволит нам выполнить следующий рефакторинг. - После рефакторинга для удаления
name
параметра:const matchSlugs5 = pipe ( propEq ('slug'), any, flip (o) (path (['node', 'categories'])), filter, o (map (prop ('node'))) )
Это самый большой шаг здесь. Мы превращаем это в функцию без точек, используя два экземпляра
o
, сокращенную двоичную версию Ramdacompose
. Это очень полезно для создания функций без точек, но может показаться довольно неясным. (Название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>