我使用Redux,我经常遇到用这样的表达式编写reducers:
return users.map(user =>
user.id !== selectedUserID ? user : {
... user,
removed: false
}
);
意图应该足够清楚:只修改数组中具有给定id的项目,同时复制其他项目。另请注意,排序很重要,否则我可能只使用了filter()
函数。
此代码段会在eslint上触发no-confusing-arrow错误,这对我来说很有意义。这也可以通过在箭头函数体周围添加圆括号来轻松解决,所以这里没什么大不了的:
return users.map(user => (
user.id !== selectedUserID ? user : {
... user,
removed: false
}
));
我还希望通过prettier解析此代码,它会自动删除箭头函数体周围的括号,返回到代码片段的第1版。
这里显而易见的解决方案是用冗长的方式编写箭头函数:
return users.map(user => {
if(user.id !== selectedUserID) {
return user; // unmodified user
}
return {
... user,
removed: false
};
});
但老实说,我觉得它有点太笨拙了。
除了特定的上下文和使用的工具(eslint和更漂亮,可以配置不同/关闭/无论如何),有没有更好的方法来写这个?
在我最疯狂的梦想中,它存在一个具有类似于:
的签名的函数Array.mapIf(callback, condition)
循环数组中的所有元素,并仅将callback
函数调用给满足给定condition
的函数,同时返回未修改的其他元素。
我显然可以自己编写这样的函数,但也许其他函数式语言中已经存在一些值得关注的一般文化/灵感。
答案 0 :(得分:3)
没有这样的原生功能,因为您可以自己轻松实现它:
const mapWhen = (p, f) => xs => xs.map(x => p(x) ? f(x) : x);
const users = [
{id: 1, removed: true},
{id: 2, removed: true},
{id: 3, removed: true}
];
console.log(
mapWhen(
({id}) => id === 2,
user => Object.assign({}, user, {removed: false})
) (users)
);
我选择mapWhen
作为名称而不是mapIf
,因为后者意味着有一个else
分支。
使用成熟的函数式语言,您可能会使用functional lense来解决此问题。不过,我认为,mapWhen
足以满足您的需求并且更具惯用性。
答案 1 :(得分:2)
在红宝石中,你会返回类似
的内容users.dup.select {|u| u.id == selected_user_id }.each {|u| u.removed = false }
在这种情况下, dup
很重要,因为在Redux中,您希望返回一个新数组而不是修改原始数组,因此首先必须创建原始数据的副本。请参阅https://stackoverflow.com/a/625746/1627766(注意讨论与此非常相似)
在你的情况下,我会使用:
const users = [
{id: 1, removed: true},
{id: 2, removed: true},
{id: 3, removed: true}
];
const selectedUserId = 2;
console.log(
users.map(
(user) => ({
...user,
removed: (user.id !== selectedUserId ? false : user.removed)
})
)
);
答案 2 :(得分:0)
如果需要,您可以使用Object.assign
添加属性。
return users.map(user =>
Object.assign({}, user, user.id === selectedUserID && { removed: false })
);
答案 3 :(得分:0)
我将提供这个作为@ ftor实际答案的补充 - 这个片段展示了如何使用泛型函数来完成某种结果
const identity = x =>
x
const comp = ( f, g ) =>
x => f ( g ( x ) )
const compose = ( ...fs ) =>
fs.reduce ( comp, identity )
const append = ( xs, x ) =>
xs.concat ( [ x ] )
const transduce = ( ...ts ) => xs =>
xs.reduce ( compose ( ...ts ) ( append ), [] )
const when = f =>
k => ( acc, x ) => f ( x ) ? k ( acc, x ) : append ( acc, x )
const mapper = f =>
k => ( acc, x ) => k ( acc, f ( x ) )
const data0 =
[ { id: 1, remove: true }
, { id: 2, remove: true }
, { id: 3, remove: true }
]
const data1 =
transduce ( when ( x => x.id === 2)
, mapper ( x => ( { ...x, remove: false } ) )
)
(data0)
console.log (data1)
// [ { id: 1, remove: true }
// , { id: 2, remove: false }
// , { id: 3, remove: true }
// ]
但是,我认为我们可以通过更通用的行为改进when
,我们需要的只是一些发明类型Left
和Right
的帮助 - 在此之后展开完整的代码段摘录
const Left = value =>
({ fold: ( f, _ ) =>
f ( value )
})
const Right = value =>
({ fold: ( _, f ) =>
f ( value )
})
// a more generic when
const when = f =>
k => ( acc, x ) => f ( x ) ? k ( acc, Right ( x ) ) : k ( acc, Left ( x ) )
// mapping over Left/Right
mapper ( m =>
m.fold ( identity // Left branch
, x => ( { ...x, remove: false } ) // Right branch
) )
const Left = value =>
({ fold: ( f, _ ) =>
f ( value )
})
const Right = value =>
({ fold: ( _, f ) =>
f ( value )
})
const identity = x =>
x
const comp = ( f, g ) =>
x => f ( g ( x ) )
const compose = ( ...fs ) =>
fs.reduce ( comp, identity )
const append = ( xs, x ) =>
xs.concat ( [ x ] )
const transduce = ( ...ts ) => xs =>
xs.reduce ( compose ( ...ts ) ( append ), [] )
const when = f =>
k => ( acc, x ) => f ( x ) ? k ( acc, Right ( x ) ) : k ( acc, Left ( x ) )
const mapper = f =>
k => ( acc, x ) => k ( acc, f ( x ) )
const data0 =
[ { id: 1, remove: true }
, { id: 2, remove: true }
, { id: 3, remove: true }
]
const data1 =
transduce ( when ( x => x.id === 2 )
, mapper ( m =>
m.fold ( identity
, x => ( { ...x, remove: false } )
) )
)
(data0)
console.log (data1)
// [ { id: 1, remove: true }
// , { id: 2, remove: false }
// , { id: 3, remove: true }
// ]
所以你的最终功能可能看起来像这样
const updateWhen = ( f, records, xs ) =>
transduce ( when ( f )
, mapper ( m =>
m.fold ( identity
, x => ( { ...x, ...records } )
) )
) ( xs )
你会在你的减速机中称它为
const BakeryReducer = ( donuts = initState , action ) =>
{
switch ( action.type ) {
case ApplyGlaze:
return updateWhen ( donut => donut.id === action.donutId )
, { glaze: action.glaze }
, donuts
)
// ...
default:
return donuts
}
}