Ramda:如何让这个命令式 reducer 更具声明性?

Ramda: How can I make this imperative reducer more declarative?

我有以下reducer函数:

减速器的第一个参数是聚合值,第二个参数是下一个值。下面的 reducer 函数正在减少相同的 reaction 参数但聚合 state$ 值。每个 reducer 函数都会产生一个新的聚合值。

/**
 * Applies all the reducers to create a state object.
 */
function reactionReducer(reaction: ReactionObject): ReactionObject {
    let state$ = reactionDescriptionReducer({}, reaction);
    state$ = reactionDisabledReducer(state$, reaction);
    state$ = reactionIconReducer(state$, reaction);
    state$ = reactionOrderReducer(state$, reaction);
    state$ = reactionStyleReducer(state$, reaction);
    state$ = reactionTitleReducer(state$, reaction);
    state$ = reactionTooltipReducer(state$, reaction);
    state$ = reactionVisibleReducer(state$, reaction);
    return state$;
}

const state = reactionReducer(value);

上面的工作,但功能是固定的与减速器列表。看来我应该可以用 RamdaJS 做这样的事情。

const state = R.????({}, value, [reactionDescriptionReducer
    reactionDisabledReducer,
    reactionIconReducer,
    reactionOrderReducer,
    reactionStyleReducer,
    reactionTitleReducer,
    reactionTooltipReducer,
    reactionVisibleReducer]);

我是 RamdaJS 的新手,如果这是一个菜鸟问题,请原谅我。

我如何仅使用 RamdaJS 执行一个 reducer 链?

我的第一次尝试根本不会涉及 Ramda,只是一个简单的:

const makeReducer = (...fns) => (x) => fns .reduce ( (s, fn) => fn (s, x), {} )

const fn = makeReducer (
  (state$, reaction) => ({...state$, foo: `<<-${reaction.foo}->>`}),
  (state$, reaction) => ({...state$, bar: `=*=${reaction.bar}=*=`}),
  (state$, reaction) => ({...state$, baz: `-=-${reaction.baz}-=-`})
)

console .log (
  fn ( {foo: 'a', bar: 'b', baz: 'c'} )
) //~> {foo: '<<-a->>', bar: '=*=b=*=', baz: '-=-c-=-'}

虽然您可以选择使用 Ramda 的 reduce and flip,但似乎他们不会在这里添加太多内容。

and 通过组合两 (2) 个输入缩减器 fg -

构造一个新的缩减器 (r, x) => ...
const and = (f, g) =>
  (r, x) => g (f (r, x), x)

all,通过使用and,通过组合任意数量的reducer构造一个新的reducer -

const identity = x =>
  x

const all = (f = identity, ...more) =>
  more .reduce (and, f)

使用 all -

定义 myReducer
const myReducer =
  all
    ( reactionDisabledReducer
    , reactionIconReducer
    , reactionOrderReducer
    // ...
    )

给定这三 (3) 个减速器的模拟实现 -

const reactionDisabledReducer = (s, x) =>
  x < 0
    ? { ...s, disabled: true }
    : s

const reactionIconReducer = (s, x) =>
  ({ ...s, icon: `${x}.png` })

const reactionOrderReducer = (s, x) =>
  x > 10
    ? { ...s, error: "over 10" }
    : s

运行 myReducer 查看输出

const initState =
  { foo: "bar" }

myReducer (initState, 10)
// { foo: 'bar', icon: '10.png' }

myReducer (initState, -1)
// { foo: 'bar', disabled: true, icon: '-1.png' }

myReducer (initState, 100)
// { foo: 'bar', icon: '100.png', error: 'over 10' }

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

const identity = x =>
  x

const and = (f, g) =>
  (r, x) => g (f (r, x), x)

const all = (f, ...more) =>
  more .reduce (and, f)

const reactionDisabledReducer = (s, x) =>
  x < 0
    ? { ...s, disabled: true }
    : s

const reactionIconReducer = (s, x) =>
  ({ ...s, icon: `${x}.png` })

const reactionOrderReducer = (s, x) =>
  x > 10
    ? { ...s, error: "over 10" }
    : s

const myReducer =
  all
    ( reactionDisabledReducer
    , reactionIconReducer
    , reactionOrderReducer
    // ...
    )

const initState =
  { foo: "bar" }

console .log (myReducer (initState, 10))
// { foo: 'bar', icon: '10.png' }

console .log (myReducer (initState, -1))
// { foo: 'bar', disabled: true, icon: '-1.png' }

console .log (myReducer (initState, 100))
// { foo: 'bar', icon: '100.png', error: 'over 10' }


您可以为 andall 选择您喜欢的任何名称。我可以将它们视为 reducer 模块的一部分,例如 reducer.andreducer.all

在这里使用 Ramda 的一个选择是利用它支持将函数作为 monad 实例传递给 R.chain(也称为 Reader monad)这一事实。

这让您可以将共享一些公共环境的多个函数排序在一起 - 在您的例子中,reaction

我们可以利用 R.pipeWith(R.chain) 来组合一系列接受一些输入的函数(例如,您的 $state 线程遍历每个函数)和 returns 一个接受输入的函数环境,产生一个结果传递给管道中的下一个函数。

// Some mock functions to demonstrate

const reactionDescriptionReducer = ({...state}, reaction) =>
  ({ description: reaction, ...state })

const reactionDisabledReducer = ({...state}, reaction) =>
  ({ disabled: reaction, ...state })

const reactionIconReducer = ({...state}, reaction) =>
  ({ icon: reaction, ...state })

// effectively `R.pipeK`
const kleisli = R.pipeWith(R.chain)

// we need the functions going into chain to be curried
const curried = f => a => b => f(a, b)

// finally, compose the series of functions together
const reactReducer = kleisli([
  curried(reactionDescriptionReducer),
  curried(reactionDisabledReducer),
  curried(reactionIconReducer)
])({})

// and if all goes well...
console.log(
  reactReducer("someCommonReactionValue")
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>