Ramda - 如何将新属性添加到嵌套 object

Ramda - how to add new properties to nested object

我正在尝试将新属性 widthheight 添加到嵌套的 object 中。

我的数据结构是这样的:

const graph = {
  id: 'root',
  children: [
    {
      id: 'n1'
    },
    {
      id: 'n2'
    }
  ]
};

我正在尝试根据 id

向每个 child 添加独特的 widthheight 属性

我试过了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.overR.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'}]

但是接下来我们需要处理我们的 widthheight 属性。一种非常有用的方法是关注具有给定特定属性的对象,这样我们 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 的答案涉及更多代码行。但它创建了有用的、可重复使用的镜头创建器,lensMatchlensIdlensProps

注意:如果我们尝试使用未知 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}]}