如何用Ramda模仿lodash.set

How to imitate lodash.set with Ramda

正在学习Ramda,一头雾水。我想创建一个类似于 lodash.set 函数的集合函数。但是,当我在对象中存在的路径上尝试以下操作时,它似乎按预期工作,但是当我使用它创建新路径时,它添加了这个奇怪的数组。

const R = require('ramda')

const set = (object, path, value) => R.set(R.lensPath(path), value, object);

const foo = {
  moo: {
    goo: 'goo'
  }
}


set(foo, ['moo', 'goo', 'boo'], 'roo'); // { moo: { goo: { '0': 'g', '1': 'o', '2': 'o', boo: 'roo' } } }

所以结果是: // { moo: { goo: { '0': 'g', '1': 'o', '2': 'o', boo : 'roo' } } }

当我期望它是:// { moo: { goo: { boo: 'roo' } } }

为什么要按索引添加这些字符?如何使用 Ramda 完成 lodash.set 功能?

It seems like unwanted behavior. Why would anyone want Ramda to coerce the string?

我认为这是一个不同的问题。当 foo.moo.goo 是一个字符串时,您正在编写的代码应该做一些模糊地等同于 foo.moo.goo.boo = 'roo' 的事情。那当然会引发错误,例如 Cannot create property 'boo' on string 'goo'.

Lodash 通过类似 "Oh, you must have meant foo.moo.goo = {boo: 'roo'}." 的回答来回答这个问题,这是一个完全合理的猜测。但这是一个猜测。图书馆是否应该抛出上述错误?这可能是合乎逻辑的事情。

Ramda(免责声明:我是其作者之一)做出了不同的选择。它假设你是认真的。您想要更新 foo.moogoo 属性,方法是将其 boo 属性 设置为 'roo'。然后它会这样做。但是,当它像这样更新 属性 时,与其他地方一样,它不会改变您的原始数据,而是为您构建一个新的输出,复制旧对象的属性,除了它适当设置的这个新路径.那么,您的旧对象 ('goo') 具有三个属性,{0: 'g'}{1: 'o'}{2: 'o'},其中 Ramda 的 assocPath(public 函数lensPath 也使用它来完成这项工作)然后与您的 {boo: 'roo'}.

组合成一个对象

但我在这里夸大了。 Ramda 从未真正做出过这个选择。这只是实施中出现的问题。 assocPath 只知道两种类型:数组和对象。它只知道如何重建这些类型。实际上它可以用作数组和其他。由于您的 foo.moo 不是数组,因此它会将其视为对象。

如果您希望改变这种行为,new issue, or even better a pull request 会得到公平的听证会。我可以向你保证。

但我预计会遇到很多阻力。

Ramda 的哲学与 lodash 的哲学有很大不同。 lodash 强调灵活性。例如,set 允许您将路径写为数组或字符串,其两个示例

_.set(object, 'a[0].b.c', 4);

_.set(object, ['x', '0', 'y', 'z'], 5);

而且很多函数都有可选参数。它自由地改变提供给它的数据。它的设计目标是 "providing quality utility methods to as many devs as possible with a focus on consistency, compatibility, customization, and performance." 作为其创始人 once said

相比之下,Ramda 不太担心灵活性。 Ramda 的一个更重要的目标是简单。它的 API 没有可选参数。当它允许一个参数有多种类型时,这只是因为它们共享更高的抽象。提供给 assocPath 的路径是一个数组,而且只是一个数组;它通过注意每个路径元素是字符串还是整数来处理对象与数组。当然,Ramda 永远不会改变您的输入数据。

Ramda 也对手持不感兴趣。这种理念通常是垃圾进,垃圾出。这个案子似乎进入了那个领域。 Ramda 会自愿将 {moo: {goo: 'goo'}} 转换为 {moo: {goo: {boo: 'roo'}}}。但是你必须更明确地告诉它这样做:assoc(['moo', 'goo'], {boo: 'roo'})

因此,更改此设置的请求可能很难说服...但这是一群友好的人。如果您认为重要,请随时提出来。

I feel like I just have to import set function from lodash to prevent unexpected behavior.

但请记住,行为有多么不同。最大的区别是 lodash 正在改变它的输入,而 Ramda 不会这样做。他们对哪些价值要增强,哪些价值要取代(如当前示例)有不同的想法。当然,他们的签名是不同的。它们在数组索引方面有不同的行为。 (例如,我想不出在 lodash 的 set 中添加一个带有字符串键 '0' 的对象的方法。当你调用 assocPath(['grid', 'width'], newVal, myObj) 之类的东西时,Ramda 当然不会包含一个构造的矩形,而 lodash 会很乐意改变内部 Rectangle 对象。)

换句话说,它们是不同的行为,为不同的目的而设计。如果 lodash 的行为是您想要的,请务必包含它。但是,如果您将 Ramda 用于大部分实用程序工作,请注意它们的理念有何不同。