Ramda — 根据允许列表切换对象的属性

Ramda — Toggle properties of an object based on an allow list

我有以下一个对象和一个数组的情况:

const currenObject = {
  errors: false,
  values: false,
  warnings: false,
};

const template = ['values', 'warnings'];

如何映射 currentObject 并且如果 属性 存在于 template 数组中以将相应的 属性 更改为 true。所以上面的例子应该产生:

const currenObject = {
  errors: false,
  values: true,
  warnings: true,
};

这对于 vanilla javascript 来说是微不足道的,但我们的目标是通过 Ramda JS 来实现它。 提前谢谢你。

template 映射到 [key, T] 的数组,使用 R.fromPairs 转换为对象,然后使用 R.evolve 创建更新的对象:

const { pipe, map, of, append, T, fromPairs, evolve } = R;

const fn = pipe(map(pipe(of, append(T))), fromPairs, evolve);

const currenObject = {
  errors: false,
  values: false,
  warnings: false,
};

const template = ['values', 'warnings'];

const result = fn(template)(currenObject);

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>

鉴于:

const obj = { errors: false, values: false, warnings: false };
const tpl = ['values', 'warnings'];

您可以从 tpl 中挑选 obj 中的属性:

pick(tpl, obj);
//=> {"values": false, "warnings": false}

并将它们设置为 true

map(T, pick(tpl, obj));
//=> {"values": true, "warnings": true}

剩下的就是将该对象合并到原始对象中:

const toggle = curry((t, o) => mergeRight(o, map(T, pick(t, o))));

toggle(tpl, obj);
//=> {"errors": false, "values": true, "warnings": true}

其他答案假定您要将相关值设置为 true

但标题暗示你想切换它们当前的布尔值。这是一个这样做的版本,如果我将其中一个输入的目标值切换为以 true.

开头,这将更加明显

const toggleFields = compose (evolve, compose (mergeAll, map (objOf (__, not))))

const currentObject = {
  errors: false,
  values: true,
  warnings: false,
};

const template = ['values', 'warnings'];

console .log (toggleFields (template) (currentObject))
// => {
//      errors: false,  // ignored
//      values: false,  // toggled from true
//      warnings: true  // toggled from false
//    }
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {compose, evolve, mergeAll, map, objOf, __, not} = R </script>

我们的工作是将['values', 'warnings']变成

evolve ({
  values: not,
  warnings: not
})

其中 not is Ramda's boolean-toggle function and evolve 采用配置 object 将名称映射到转换器函数和 returns 一个采用 object 并克隆它的函数,转换任何指定的属性通过适当的功能。然后我们简单地用我们的源调用这个函数 object.

如果正好遇到字符荒,可以用Ramda curried,二元组合函数,o, in place of the venerable compose本身:

const toggleFields = o (evolve, o (mergeAll, map (objOf (__, not))))

根据口味,我们也可以将 objOf (__, not) 替换为 flip (objOf) (not)

推导

这个版本看起来很密集。我原来是这样写的:

const fn = (template) => (currentObject) => evolve (pipe (
  map (objOf (__, not)),
  mergeAll
) (template)) (currentObject)

但是在这里,如果我们稍微眯起眼睛,我们可以看到这个形式:

(x) => (y) => f (g (x)) (y)

其中x代表templatey代表currentObjectf代表evolveg代表其他一切。

并且当我们有一个仅在 body 末尾应用的最终参数时,我们可以简单地删除它,因此与

相同
(x) => f (g (x))

但这只是fg的组合。所以我们可以写成

compose (f, g)

并替换回原来的我们得到

compose (
  evolve,
  pipe (map (objOf (__, not)), mergeAll) 
)

我通常不喜欢在管道中混合使用 composepipe。我对 one-liners 使用 compose,对更长的函数使用 pipe;这对我来说开始看起来像 one-liner。所以我们交换 mapmergeAll 的顺序并切换到 compose,产生这个:

compose (evolve, compose (mergeAll, map (objOf (__, not)))

原版

作为 Ramda 的创始人和主要作者,我实际上是经常建议人们试图将它应用到他们不需要的地方的人。但在这里我认为这个版本比我提出的第一个 vanilla JS 版本更干净:

const toggle = (template) => (currentObject) =>
  Object .fromEntries (
    Object .entries (currentObject) 
      .map (([k, v]) => [k, template .includes (k) ? !v : v] )
  )

并不是说这样不好,我不会为了这个小小的改进而引入Ramda。但如果我已经在使用 Ramda,那么 Ramda 版本似乎确实有所改进。

有模块

这是解决问题的基于模块的方法 -

// obj.js

function get (t, k) {
  return t[k]
}

function set (t, k, v) {
  return { ...t, [k]: v }
}

function update (t, k, f) {
  return set(t, k, f(get(t, k)))
}

export { get, set, update }

我们建立了与对象交互的通用方式。现在我们可以使用 update -

轻松编写 toggle
// main.js

import { update } from "./obj.js"

function toggle (t, k) {
  return update(t, k, (v = false) => !v)
}

const input =
  { errors: false, values: false, warnings: false }
  
const output =
  ["values", "warnings"].reduce(toggle, input)

console.log(output)
{
  errors: false,
  values: true,
  warnings: true
}

展开下面的代码片段以在浏览器中验证结果 -

function get (t, k) {
  return t[k]
}

function set (t, k, v) {
  return { ...t, [k]: v }
}

function update (t, k, f) {
  return set(t, k, f(get(t, k)))
}

function toggle (t, k) {
  return update(t, k, v => !v)
}

const input =
  { errors: false, values: false, warnings: false }
  
const output =
  ["values", "warnings"].reduce(toggle, input)

console.log(output)

与 ramda

如果您更喜欢 Ramda,则有完全(或接近)等价物 -

obj module ramda
obj.get R.prop
obj.set R.assoc
obj.update R.evolve

知道这一点,我们可以选择直接使用 ramda 或者我们可以使用 ramda 本身实现 obj 模块 -

// obj

import { assoc, prop, evolve } from "ramda"

function get (t, k) {
  return prop(k, t)
}

function set (t, k, v) {
  return assoc(k, v, t)
}

function update (t, k, f) {
  return evolve({ [k]: f }, t)
}

export { get, set, update }

这里的区别是 obj 的用户不知道 ramda 正在幕后使用。这是设计自定义模块的有用方法,可以更有效地适合您的程序或模式,同时利用其他库或模式的优势。