簡體   English   中英

使用Ramdajs將命令式轉換為功能樣式范例

[英]Converting Imperative to Functional style paradigm using Ramdajs

以下腳本創建一個對象,該對象過濾一些輸入數據。 它使用幾個嵌套的forEach以聲明的方式進行編碼。

我想知道在使用ramdajslodash重新編寫此代碼時使用哪個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遍歷復雜的結構既困難又優雅。 要使用的鏡頭,修改結構applySpecevolve是值得推薦的,這些都是返回對象的新版本與修改后的值是非常有用的。 但是您正在尋求以與原始樹非常不同的方式轉換數據,我認為這是AST。 在Ramda中, pipecompose是必不可少的,這使得通過編寫小的函數來構造代碼成為可能。 為了使用樹,我使用converge進行分支,使用objOfzipObj創建新對象。 還要mapreduce以使用列表。

在此示例中,我將使用以下組合策略:

          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)

返回0100 ,或的結果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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM