Я думаю, что лучше всего это сделать с помощью пользовательского lens
. Здесь я пишу создатель объектива (idInObjLens
), который фокусируется на объекте с правильным идентификатором, независимо от того, в какую группу он попадает. Затем я пишу myLens
, чтобы принять идентификатор и имя свойства и вернуть вам объектив, который фокусируетсяна это свойство объекта.
С ними мы можем использовать Рамда view
, set
и over
чтобы увидеть значение, установите его или обновите значение с помощью функции:
const idInObjLens = (id) => lens (
(obj) => {
let index = -1
const child = find (value => (index = findIndex (propEq ('id', id), value)) > -1, values (obj) )
if (child) return child [index]
},
(val, obj) => {
let index = -1
const [key, value] = find (([key, value]) => (index = findIndex (propEq ('id', id), value)) > -1, toPairs (obj) )
return assoc (key, update (index, val, value), obj)
},
)
const myLens = (id, key) => compose (idInObjLens (id), lensProp (key))
const stuff = {"31": [{"id": "11", "title": "ramda heeeelp"}, {"id": "12", "title": "ramda 123"}], "33": [{"id": "3", "title": "..."}], "4321": [{"id": "1", "title": "hello world"}]};
[
view (myLens ('12', 'title'), stuff),
set (myLens ('3', 'title'), 'new heading 123', stuff),
over (myLens ('1', 'title'), toUpper, stuff),
] .forEach (x => console .log (x))
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {lens, find, findIndex, propEq, values, toPairs, assoc, update, compose, lensProp, view, set, over, toUpper } = R</script>
Обратите внимание, что set
и over
будут работать, только если этот идентификатор действительно существует. Сначала вы можете проверить, используя view
.
myLens
просто;это просто, как работает состав линз. (Обратите внимание, что кажется, что это происходит в обратном направлении от обычной композиции; технические причины интересны, но выходят за рамки SO-ответа.) Но idInObjLens
более сложный. Как и во всех объективах, требуется геттер и сеттер. Оба они одновременно находят ключ объекта, который содержит элемент с идентификатором и индексом этого ключа в массиве, связанном с этим ключом объекта. Получатель просто возвращает значение. Сеттер использует assoc
для обновления внешнего объекта и update
для обновления массива внутри него. Все остальные вложенные объекты просто возвращаются по ссылке.
Это не тот код, которым можно гордиться. Это работает, и, конечно, это главное. Но мне действительно не нравится вычислять индекс массива как побочный эффект вызова find
. Тем не менее, вычисление этого во второй раз кажется излишним. Мне также не очень нравится имя idInObjLens
, и я всегда чувствую, что если у меня нет хорошего имени, я упускаю что-то фундаментальное. (У меня нет того же возражения против myLens
, так как я предполагаю, что у вас будет более подходящее название для этого варианта использования.)
Большая разница между этим и решением от Hitmand заключается в том, чтоэтот объектив не требует, чтобы вы знали заранее, какая клавиша во внешнем объекте удерживает предмет с вашим идентификатором. Это добавляет сложности к решению, но делает его API более гибким.