[英]Converting Imperative to Functional style paradigm using Ramdajs
以下脚本创建一个对象,该对象过滤一些输入数据。 它使用几个嵌套的forEach
以声明的方式进行编码。
我想知道在使用ramdajs或lodash重新编写此代码时使用哪个API,特别是我想了解在这种情况下使用管道是否合适,否则我会感兴趣。
将提供一个示例代码(特别是对于ramdajs)。 谢谢。
var data = { "type": "stylesheet", "stylesheet": { "rules": [{ "type": "keyframes", "name": "bounce", "keyframes": [{ "type": "keyframe", "values": [ "from", "20%", "53%", "80%", "to" ], "declarations": [{ "type": "declaration", "property": "animation-timing-function", "value": "cubic-bezier(0.215, 0.610, 0.355, 1.000)", "position": { "start": { "line": 3, "column": 5 }, "end": { "line": 3, "column": 72 } } }, { "type": "declaration", "property": "transform", "value": "translate3d(0,0,0)", "position": { "start": { "line": 4, "column": 5 }, "end": { "line": 4, "column": 34 } } }], "position": { "start": { "line": 2, "column": 3 }, "end": { "line": 5, "column": 4 } } }, { "type": "keyframe", "values": [ "40%", "43%" ], "declarations": [{ "type": "declaration", "property": "animation-timing-function", "value": "cubic-bezier(0.755, 0.050, 0.855, 0.060)", "position": { "start": { "line": 8, "column": 5 }, "end": { "line": 8, "column": 72 } } }, { "type": "declaration", "property": "transform", "value": "translate3d(0, -30px, 0)", "position": { "start": { "line": 9, "column": 5 }, "end": { "line": 9, "column": 40 } } }], "position": { "start": { "line": 7, "column": 3 }, "end": { "line": 10, "column": 4 } } }, { "type": "keyframe", "values": [ "70%" ], "declarations": [{ "type": "declaration", "property": "animation-timing-function", "value": "cubic-bezier(0.755, 0.050, 0.855, 0.060)", "position": { "start": { "line": 13, "column": 5 }, "end": { "line": 13, "column": 72 } } }, { "type": "declaration", "property": "transform", "value": "translate3d(0, -15px, 0)", "position": { "start": { "line": 14, "column": 5 }, "end": { "line": 14, "column": 40 } } }], "position": { "start": { "line": 12, "column": 3 }, "end": { "line": 15, "column": 4 } } }, { "type": "keyframe", "values": [ "90%" ], "declarations": [{ "type": "declaration", "property": "transform", "value": "translate3d(0,-4px,0)", "position": { "start": { "line": 18, "column": 5 }, "end": { "line": 18, "column": 37 } } }], "position": { "start": { "line": 17, "column": 3 }, "end": { "line": 19, "column": 4 } } }], "position": { "start": { "line": 1, "column": 1 }, "end": { "line": 20, "column": 2 } } }, { "type": "rule", "selectors": [ ".bounce" ], "declarations": [{ "type": "declaration", "property": "animation-name", "value": "bounce", "position": { "start": { "line": 23, "column": 3 }, "end": { "line": 23, "column": 25 } } }, { "type": "declaration", "property": "transform-origin", "value": "center bottom", "position": { "start": { "line": 24, "column": 3 }, "end": { "line": 24, "column": 34 } } }], "position": { "start": { "line": 22, "column": 1 }, "end": { "line": 25, "column": 2 } } }, { "type": "keyframes", "name": "spark", "keyframes": [{ "type": "keyframe", "values": [ "0%", "50%" ], "declarations": [{ "type": "declaration", "property": "transform", "value": "translate3d(0,0,0)", "position": { "start": { "line": 29, "column": 5 }, "end": { "line": 29, "column": 34 } } }], "position": { "start": { "line": 28, "column": 3 }, "end": { "line": 30, "column": 4 } } }, { "type": "keyframe", "values": [ "100%" ], "declarations": [{ "type": "declaration", "property": "transform", "value": "translate3d(0,-4px,0)", "position": { "start": { "line": 32, "column": 5 }, "end": { "line": 32, "column": 37 } } }], "position": { "start": { "line": 31, "column": 3 }, "end": { "line": 33, "column": 4 } } }], "position": { "start": { "line": 27, "column": 1 }, "end": { "line": 34, "column": 2 } } }, { "type": "rule", "selectors": [ ".spark" ], "declarations": [{ "type": "declaration", "property": "animation-name", "value": "spark", "position": { "start": { "line": 37, "column": 3 }, "end": { "line": 37, "column": 24 } } }, { "type": "declaration", "property": "transform-origin", "value": "center center", "position": { "start": { "line": 38, "column": 3 }, "end": { "line": 38, "column": 34 } } }], "position": { "start": { "line": 36, "column": 1 }, "end": { "line": 39, "column": 2 } } }], "parsingErrors": [] } }; var result = {}; var kfs = data.stylesheet.rules.filter(function(rule) { return rule.type === 'keyframes' }); kfs.forEach(function(kf) { result[kf.name] = []; kf.keyframes.forEach(function(kfi) { kfi.values.forEach(function(v) { var r = {}; var vNew; vNew = v; if (v === 'from') { vNew = 0; } else if (v === 'to') { vNew = 100; } else { vNew = parseFloat(v); } r.offset = vNew; kfi.declarations.forEach(function(d) { r[d.property] = d.value; }); result[kf.name].push(r); }); }); }); console.log(result);
编辑:
到目前为止,我已经能够在ramdajs中实现以下结果:
var rulesLense = R.lensPath(['stylesheet', 'rules']);
var ruleView = R.view(rulesLense, obj);
var keyframes = R.filter(R.propEq('type', 'keyframes'));
var groupByKeyframe = R.groupBy(keyframe => {
return R.prop('name', keyframe);
});
var process = R.pipe(
keyframes,
groupByKeyframe
);
var result = process(ruleView);
仅使用Ramda遍历复杂的结构既困难又优雅。 要使用的镜头,修改结构applySpec
和evolve
是值得推荐的,这些都是返回对象的新版本与修改后的值是非常有用的。 但是您正在寻求以与原始树非常不同的方式转换数据,我认为这是AST。 在Ramda中, pipe
和compose
是必不可少的,这使得通过编写小的函数来构造代码成为可能。 为了使用树,我使用converge
进行分支,使用objOf
和zipObj
创建新对象。 还要map
并reduce
以使用列表。
在此示例中,我将使用以下组合策略:
transformAST
^
|
|
getContentOfKeyframes
^ ^
| |
| |
processKeyframe processAnimation
首先,让我们创建一个函数,该函数接收一个values
数组和一个declarations
数组,它返回一个数组,该数组的第一个位置具有转换后的值的数组,第二个位置是一个对象,其中键是声明值property
,值是其对应的声明value
。
var processKeyframe = (vals, declarations) => [
// map each value
R.map(R.cond([
[R.equals('from'), R.always(0)],
[R.equals('to'), R.always(100)],
[R.T, parseFloat]
]), vals),
// collect all property value pairs and merge in one object
R.reduce(R.merge, {},
R.map(R.converge(R.objOf, [
R.prop('property'),
R.prop('value')
]), declarations))
]
现在,让我们创建一个处理动画的函数,它接收offsets
数组和一个带有转换的对象,返回带有签名{offset: offset, ...trasformations}
的新对象数组。
var processAnimation = (offsets, transf) =>
R.map(R.pipe(
R.objOf('offset'),
R.merge(transf)), offsets)
接下来,通过组合前面的两个函数来映射每个关键帧
var getContentOfKeyframes = R.map(R.pipe(
// process keyframes
R.converge(processKeyframe, [
R.prop('values'),
R.prop('declarations')
]),
// process animations
R.converge(processAnimation, [
R.nth(0),
R.nth(1)
])))
最后,我们定义一个函数,该函数从data
中获取所需的属性,汇总每个关键帧,最后在最后一个阶段给出所需的格式。
var transformAST = R.pipe(
// get `stylesheet.rules` property
R.path(['stylesheet', 'rules']),
// get only object whose `type` property is `keyframes`
R.filter(R.propEq('type', 'keyframes')),
// map each item in `keyframes` collection
// to an object {name: keyframe.name, content: [contentOfkeyframes] }
R.map((keyframe) => ({
name : keyframe.name,
content : getContentOfKeyframes(keyframe.keyframes)
})),
// finally make a new object using animation `name` as keys
// and using a flatten content as values
R.converge(R.zipObj, [
R.map(R.prop('name')),
R.map(R.pipe(R.prop('content'), R.flatten))
]))
现在,您可以处理直接传递data
对象的AST。
var result = transformAST(data)
全部一起。
var processKeyframe = (vals, declarations) => [
R.map(R.cond([
[R.equals('from'), R.always(0)],
[R.equals('to'), R.always(100)],
[R.T, parseFloat]
]), vals),
R.reduce(R.merge, {},
R.map(R.converge(R.objOf, [
R.prop('property'),
R.prop('value')
]), declarations))
]
var processAnimation = (offsets, transf) =>
R.map(R.pipe(
R.objOf('offset'),
R.merge(transf)), offsets)
var getContentOfKeyframes = R.map(R.pipe(
R.converge(processKeyframe, [
R.prop('values'),
R.prop('declarations')
]),
R.converge(processAnimation, [
R.nth(0),
R.nth(1)
])))
var transformAST = R.pipe(
R.path(['stylesheet', 'rules']),
R.filter(R.propEq('type', 'keyframes')),
R.map((keyframe) => ({
name : keyframe.name,
content : getContentOfKeyframes(keyframe.keyframes)
})),
R.converge(R.zipObj, [
R.map(R.prop('name')),
R.map(R.pipe(R.prop('content'), R.flatten))
]))
var result = transformAST(data)
我的版本最终看起来与Yosbel Marin的版本完全不同。
const transform = pipe(
path(['stylesheet', 'rules']),
filter(where({'type': equals('keyframes')})),
groupBy(prop('name')),
map(map(kf => map(kfi => map(v => assoc('offset', cond([
[equals('from'), always(0)],
[equals('to'), always(100)],
[T, parseFloat]
])(v), pipe(
map(lift(objOf)(prop('property'), prop('value'))),
mergeAll
)(kfi.declarations)), kfi.values), kf.keyframes)
)),
map(flatten)
);
我这样做是作为代码端口,而没有真正尝试完全理解您的数据。 (我很难这么做,至少在某种程度上这是必须的,但这也是一种有趣的方式。)
前两个步骤应该很清楚,它们与先前的答案非常相似。 我们从data.stylesheet.rules
获取数据,然后对其进行过滤,以仅包括“ type”属性为“ keyframes”的那些规则。 (我选择在过滤器中使用where
,因为我发现以下内容比propEq
更具可读性: filter(where({'type': equals('keyframes')}))
,但它们的工作原理相同。之后是groupBy(prop('name'))
,这给我们留下了一个像这样的结构:
{
bounce: [obj1, obj2, ...]
spark: [objA, objB, ...]
}
接下来是转型的核心。 我将原始forEach
中的每个forEach
调用都转换为map
调用(显然不能总是这样做。)
这个:
map(v => map(lift(objOf)(prop('property'), prop('value'))), kfi.declarations)
将声明部分变成类似
[
{"animation-timing-function": "cubic-bezier(0.215, 0.610, 0.355, 1.000)",}
{transform: "translate3d(0,0,0)"},
]
通过将objOf
函数从处理标量值提升为处理返回此类值的函数,然后传入两个将接受声明的函数。 然后,此新函数接受一个声明并返回一个对象。 将其映射到声明列表将获得对象列表。 使用mergeAll
将其放入pipe
调用中会将这样的列表变成单个对象。
并且此位用单个表达式替换if (v === 'from') { ... } else if ...
代码:
cond([
[equals('from'), always(0)],
[equals('to'), always(100)],
[T, parseFloat]
])(v)
返回0
, 100
,或的结果parseFloat(v)
根据。
将其与assoc('offset')
和上一步的结果结合起来,我们得到的主要对象如下:
{
"animation-timing-function": "cubic-bezier(0.215, 0.610, 0.355, 1.000)",
offset: 0,
transform: "translate3d(0,0,0)"
}
剩下要做的唯一一件事就是清理所有这些地图留下的嵌套列表:
{
bounce: [[[obj1, obj2, ...]]]
spark: [[[objA, objB, ...]]]
}
我们通过添加map(flatten)
。
您可以在Ramda REPL上看到这一点。
我不知道是否可以合理地将其完全免费化。 我猜测这充其量是困难的,最终其可读性将大大降低。 这段代码可以很好地分解一些映射到自己的调用中的函数,但是我将其留给读者练习!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.