Ramda - 如何将新属性添加到嵌套 object
Ramda - how to add new properties to nested object
我正在尝试将新属性 width
和 height
添加到嵌套的 object 中。
我的数据结构是这样的:
const graph = {
id: 'root',
children: [
{
id: 'n1'
},
{
id: 'n2'
}
]
};
我正在尝试根据 id
向每个 child 添加独特的 width
和 height
属性
我试过了R.lensPath
。在这里你可以查看ramda editor:
const widthLens = R.curry((id, data) => R.lensPath([
'children',
R.findIndex(R.whereEq({ id }),
R.propOr([], 'children', data)),
'width',
]));
const setWidth = widthLens('n1', graph);
R.set(setWidth, '100', graph);
这几乎可以正常工作,但它只添加了 width
而且我需要遍历所有 children 和 return 相同的 object新属性。它看起来也过于复杂,因此非常欢迎任何建议。谢谢。
您可以使用 R.over
和 R.mergeLeft
将属性添加到索引处的对象:
const { curry, lensPath, findIndex, whereEq, propOr, over, mergeLeft } = R;
const graph = {"id":"root","children":[{"id":"n1"},{"id":"n2"}]};
const widthLens = curry((id, data) => lensPath([
'children',
findIndex(whereEq({ id }), propOr([], 'children', data)),
]));
const setValues = widthLens('n1', graph);
const result = over(setValues, mergeLeft({ width: 100, height: 200 }), graph);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
有几种不同的方法可以解决这个问题。但一种可能性是使用自定义镜头类型。 (这与 Ori Drori 的优秀答案大不相同,后者只是使用 Ramda 的 lensPath
。)
Ramda(免责声明:我是作者之一)仅提供几种特定类型的镜头——一种用于简单属性,另一种用于阵列索引,第三种用于更复杂的物体路径。但它允许您构建您可能需要的那些。并且镜头不仅仅针对简单的 object/array 特性而设计。将它们视为一组数据的框架,您可以专注于此。
所以我们可以写一个lens
,它专注于具有特定id的数组元素。我们需要就如何处理丢失的 ID 做出决定。我将在这里选择——如果没有找到 id——到 return undefined
以获得 get
并附加到 set
的末尾,但是有人们可能会探索的合理替代方案。
在实现上,id
并没有什么特别之处,所以我会基于一个特定的命名属性来做,并在一个单独的函数中专门化为id
。我们可以这样写:
const lensMatch = (propName) => (key) => lens (
find (propEq (propName, key)),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length (arr), val, arr)
)
const lensId = lensMatch ('id')
它会像这样工作:
const lens42 = lensId (42)
const a = [{id: 17, x: 'a'}, {id: 42, x: 'b'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
view (lens42, a) //=> {id: 42, x: 'b'}
set (lens42, {id: 42, x: 'z', foo: 'bar'}, a)
//=> [{id: 17, x: 'a'}, {id: 42, x: 'z', foo: 'bar'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
over (lens42, assoc ('foo', 'qux'), a)
//=> [{id: 17, x: 'a'}, {id: 42, x: 'b', foo: 'qux'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
但是接下来我们需要处理我们的 width
和 height
属性。一种非常有用的方法是关注具有给定特定属性的对象,这样我们 get
类似于 {width: 100, height: 200}
,然后我们将这样的对象传递给 set
。原来写的挺优雅的:
const lensProps = (props) => lens (pick (props), mergeLeft)
我们会这样使用它:
const bdLens = lensProps (['b', 'd'])
const o = ({a: 1, b: 2, c: 3, d: 4, e: 5})
view (bdLens, o) //=> {b: 2, d: 4}
set (bdLens, {b: 42, d: 99}, o) //=> {a: 1, b: 42, c: 3, d: 99, e: 5}
over (bdLens, map (n => 10 * n), o) //=> {a: 1, b: 20, c: 3, d: 40, e : 5}
结合这些,我们可以开发一个函数来使用:setDimensions ('n1', {width: 100, height: 200}, graph)
我们首先写一个镜头来处理id和我们的维度:
const lensDimensions = (id) => compose (
lensProp ('children'),
lensId (id),
lensProps (['width', 'height'])
)
然后我们通过
调用这个镜头的setter
const setDimensions = (id, dimensions, o) =>
set (lensDimensions (id), dimensions, o)
我们可以把这些放在一起
const lensMatch = (propName) => (key) => lens (
find (propEq (propName, key)),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length (arr), val, arr)
)
const lensProps = (props) => lens (pick (props), mergeLeft)
const lensId = lensMatch ('id')
const lensDimensions = (id) => compose (
lensProp ('children'),
lensId (id),
lensProps (['width', 'height'])
)
const setDimensions = (id, dimensions, o) => set (lensDimensions (id), dimensions, o)
const graph = {id: 'root', children: [{id: 'n1'}, {id: 'n2'}]}
console .log (setDimensions ('n1', {width: 100, height: 200}, graph))
//=> {id: "root", children: [{ id: "n1", height: 200, width: 100}, {id: "n2"}]}
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {find, propEq, findIndex, update, length, lens, pick, mergeLeft, compose, lensProp, set} = R </script>
这显然比 Ori Drori 的答案涉及更多代码行。但它创建了有用的、可重复使用的镜头创建器,lensMatch
、lensId
和 lensProps
。
注意:如果我们尝试使用未知 ID,这将失败。我有一个修复程序,但我现在没有时间深入研究它失败的原因,可能与镜头构成的方式有点不直观有关。如果我很快找到时间,我会重新研究它。但目前,我们可以简单地将 lensProps
更改为
const lensProps = (props) => lens (compose (pick (props), defaultTo ({})), mergeLeft)
然后一个未知的 id 将附加到末尾:
console .log (setDimensions ('n3', {width: 100, height: 200}, graph))
//=> {id: "root", children: [{id: "n1"}, {id: "n2"}, {id: "n3", width : 100, height : 200}]}
我正在尝试将新属性 width
和 height
添加到嵌套的 object 中。
我的数据结构是这样的:
const graph = {
id: 'root',
children: [
{
id: 'n1'
},
{
id: 'n2'
}
]
};
我正在尝试根据 id
width
和 height
属性
我试过了R.lensPath
。在这里你可以查看ramda editor:
const widthLens = R.curry((id, data) => R.lensPath([
'children',
R.findIndex(R.whereEq({ id }),
R.propOr([], 'children', data)),
'width',
]));
const setWidth = widthLens('n1', graph);
R.set(setWidth, '100', graph);
这几乎可以正常工作,但它只添加了 width
而且我需要遍历所有 children 和 return 相同的 object新属性。它看起来也过于复杂,因此非常欢迎任何建议。谢谢。
您可以使用 R.over
和 R.mergeLeft
将属性添加到索引处的对象:
const { curry, lensPath, findIndex, whereEq, propOr, over, mergeLeft } = R;
const graph = {"id":"root","children":[{"id":"n1"},{"id":"n2"}]};
const widthLens = curry((id, data) => lensPath([
'children',
findIndex(whereEq({ id }), propOr([], 'children', data)),
]));
const setValues = widthLens('n1', graph);
const result = over(setValues, mergeLeft({ width: 100, height: 200 }), graph);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
有几种不同的方法可以解决这个问题。但一种可能性是使用自定义镜头类型。 (这与 Ori Drori 的优秀答案大不相同,后者只是使用 Ramda 的 lensPath
。)
Ramda(免责声明:我是作者之一)仅提供几种特定类型的镜头——一种用于简单属性,另一种用于阵列索引,第三种用于更复杂的物体路径。但它允许您构建您可能需要的那些。并且镜头不仅仅针对简单的 object/array 特性而设计。将它们视为一组数据的框架,您可以专注于此。
所以我们可以写一个lens
,它专注于具有特定id的数组元素。我们需要就如何处理丢失的 ID 做出决定。我将在这里选择——如果没有找到 id——到 return undefined
以获得 get
并附加到 set
的末尾,但是有人们可能会探索的合理替代方案。
在实现上,id
并没有什么特别之处,所以我会基于一个特定的命名属性来做,并在一个单独的函数中专门化为id
。我们可以这样写:
const lensMatch = (propName) => (key) => lens (
find (propEq (propName, key)),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length (arr), val, arr)
)
const lensId = lensMatch ('id')
它会像这样工作:
const lens42 = lensId (42)
const a = [{id: 17, x: 'a'}, {id: 42, x: 'b'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
view (lens42, a) //=> {id: 42, x: 'b'}
set (lens42, {id: 42, x: 'z', foo: 'bar'}, a)
//=> [{id: 17, x: 'a'}, {id: 42, x: 'z', foo: 'bar'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
over (lens42, assoc ('foo', 'qux'), a)
//=> [{id: 17, x: 'a'}, {id: 42, x: 'b', foo: 'qux'}, {id: 99, x: 'c'}, {id: 57, x: 'd'}]
但是接下来我们需要处理我们的 width
和 height
属性。一种非常有用的方法是关注具有给定特定属性的对象,这样我们 get
类似于 {width: 100, height: 200}
,然后我们将这样的对象传递给 set
。原来写的挺优雅的:
const lensProps = (props) => lens (pick (props), mergeLeft)
我们会这样使用它:
const bdLens = lensProps (['b', 'd'])
const o = ({a: 1, b: 2, c: 3, d: 4, e: 5})
view (bdLens, o) //=> {b: 2, d: 4}
set (bdLens, {b: 42, d: 99}, o) //=> {a: 1, b: 42, c: 3, d: 99, e: 5}
over (bdLens, map (n => 10 * n), o) //=> {a: 1, b: 20, c: 3, d: 40, e : 5}
结合这些,我们可以开发一个函数来使用:setDimensions ('n1', {width: 100, height: 200}, graph)
我们首先写一个镜头来处理id和我们的维度:
const lensDimensions = (id) => compose (
lensProp ('children'),
lensId (id),
lensProps (['width', 'height'])
)
然后我们通过
调用这个镜头的setterconst setDimensions = (id, dimensions, o) =>
set (lensDimensions (id), dimensions, o)
我们可以把这些放在一起
const lensMatch = (propName) => (key) => lens (
find (propEq (propName, key)),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length (arr), val, arr)
)
const lensProps = (props) => lens (pick (props), mergeLeft)
const lensId = lensMatch ('id')
const lensDimensions = (id) => compose (
lensProp ('children'),
lensId (id),
lensProps (['width', 'height'])
)
const setDimensions = (id, dimensions, o) => set (lensDimensions (id), dimensions, o)
const graph = {id: 'root', children: [{id: 'n1'}, {id: 'n2'}]}
console .log (setDimensions ('n1', {width: 100, height: 200}, graph))
//=> {id: "root", children: [{ id: "n1", height: 200, width: 100}, {id: "n2"}]}
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {find, propEq, findIndex, update, length, lens, pick, mergeLeft, compose, lensProp, set} = R </script>
这显然比 Ori Drori 的答案涉及更多代码行。但它创建了有用的、可重复使用的镜头创建器,lensMatch
、lensId
和 lensProps
。
注意:如果我们尝试使用未知 ID,这将失败。我有一个修复程序,但我现在没有时间深入研究它失败的原因,可能与镜头构成的方式有点不直观有关。如果我很快找到时间,我会重新研究它。但目前,我们可以简单地将 lensProps
更改为
const lensProps = (props) => lens (compose (pick (props), defaultTo ({})), mergeLeft)
然后一个未知的 id 将附加到末尾:
console .log (setDimensions ('n3', {width: 100, height: 200}, graph))
//=> {id: "root", children: [{id: "n1"}, {id: "n2"}, {id: "n3", width : 100, height : 200}]}