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-=-'}
and
通过组合两 (2) 个输入缩减器 f
和 g
-
构造一个新的缩减器 (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' }
您可以为 and
和 all
选择您喜欢的任何名称。我可以将它们视为 reducer
模块的一部分,例如 reducer.and
和 reducer.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>
我有以下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-=-'}
and
通过组合两 (2) 个输入缩减器 f
和 g
-
(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' }
您可以为 and
和 all
选择您喜欢的任何名称。我可以将它们视为 reducer
模块的一部分,例如 reducer.and
和 reducer.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>