JavaScript:重新组织 canvas 以功能方式呈现

JavaScript: Re-organize canvas rendering in a functional manner

是否有更好或更“实用”的方式来组织此代码?

let activeCanvas = null;

export function createLayer() {
    if (activeCanvas)
        activeCanvas.disableInteraction();

    let newCanvas = createCanvas({ transparent: !!activeCanvas});

    let parentCanvas = activeCanvas;
    activeCanvas = newCanvas;

    return {
        stage: newCanvas.stage,
        destroy() {
            newCanvas.destroy();
            parentCanvas.enableInteraction();
            activeCanvas = parentCanvas;
        }
    }
}

这将创建一个新层和 returns 调用者的舞台以及再次销毁该层然后使父层成为活动层的能力。

层可以堆叠在一起。

...这个模块作用域的“activeCanvas”变量怎么样?有没有一种方法可以更“实用”地实现此逻辑并将其结束在某处的调用堆栈上?

更新

代码按原样运行良好 - 这只是一个关于风格、优雅以及如何以函数式方式正确编程的问题。

我对代码很满意 - 我只是觉得有一种更“开明”的方法来做,但我就是想不通。

注意:在我下面的示例中,"transparent" canvas 是蓝色的,常规的 canvas 是绿色的。禁用交互 canvas 的不透明度低于启用的交互。


你在这里基本上拥有的是一种带有一些状态封装的堆栈形式。

您关心的是:

  1. 将项目推到顶部(添加新层)
  2. 从顶部弹出一个项目(破坏一层)
  3. 跟踪当前阶段

function createLayers(canvases = []) {
  function top() {
    return canvases[canvases.length - 1];
  }

  function disableTop() {
    const last = top();
    if (last) last.disableInteraction();
  }

  function enableTop() {
    const last = top();
    if (last) last.enableInteraction();
  }

  function destroyTop() {
    const last = top();
    if (last) last.destroy();
  }

  function push() {
    disableTop();
    canvases.push(createCanvas({ transparent: !!top() }));
    enableTop();
  }

  function pop() {
    destroyTop();
    canvases.pop();
    enableTop();
  }

  return {
    push,
    pop,
    stage: () => top().stage
  };
}

const layers = createLayers();

document.querySelector("#push").addEventListener("click", layers.push);

document.querySelector("#pop").addEventListener("click", layers.pop);

var createCanvas=function(e=0){return function(a={}){const t=e;e+=1;const n=document.createElement("div");return n.className=`fake-canvas ${a.transparent?"transparent":""}`,document.body.appendChild(n),n.appendChild(document.createTextNode(t)),{__elem:n,enableInteraction(){n.classList.remove("disabled")},disableInteraction(){n.classList.add("disabled")},destroy(){n.remove()}}}}();
.fake-canvas{border:3px solid #018bbc;width:75px;height:50px;display:flex;align-items:center;justify-content:center;margin-left:10px;position:relative;}.fake-canvas.disabled{opacity:.5;pointer-events:none}body{height:100vh;display:flex;align-items:center}.fake-canvas.transparent{border-color:#2ecc71;}button{margin:5px;padding:10px;appearance:none;background-color:#018bbc;border:0;color:#fff;text-transform:uppercase}#buttons{display:flex;flex-direction:column;}
<div id="buttons"><button id="push">push</button><button id="pop">pop</button></div>

另一种理解方式是将其视为链表。

为了让事情更实用,你想在列表构造和修改中引入纯粹性和不变性。

列表永远不会发生变化,相反,任何修改都会创建一个新列表。

为了处理canvas操作,我们可以引入一个副作用函数。为了让事情完全发挥作用,我们实际上想在每个操作上创建一个带有新 canvas 的新层,但这可能很昂贵,所以我的示例在现有的 canvas.

上运行

var createCanvas=function(e=0){return function(t){const a=e;e+=1;const n=document.createElement("div");return n.className=`fake-canvas ${t.transparent?"transparent":""}`,document.body.appendChild(n),n.appendChild(document.createTextNode(a)),{__elem:n,enableInteraction(){n.classList.remove("disabled")},disableInteraction(){n.classList.add("disabled")},destroy(){n.remove()}}}}();

// function to create a layer
function layer(prev) {
  return {
    canvas: createCanvas({ transparent: !!prev }),
    prev: typeof prev === 'function' ? prev : () => prev,
  };
}

// function to compose an array of functions
function compose(...fns) {
  return function composition(arg) {
    return fns.reduceRight((prevResult, fn) => fn(prevResult), arg);
  }
}

// generic canvas-side-effect-enducing function
function canvasSideEffect(name) {
  return function sideEffect(l) {
    if (l && l.canvas) {
      l.canvas[name]();
    }
    return l;
  }
}

// create functions to perform canvas side-effect operations
const enable = canvasSideEffect("enableInteraction");
const disable = canvasSideEffect("disableInteraction");
const destroy = canvasSideEffect("destroy");

const push = compose(
  enable, // then we enable the new layer's canvas
  layer,  // then we create a new layer
  disable // first we disable the top layer's canvas
)

const pop = compose(
  enable,                // then we enable the new top layer's canvas
  l => l ? l.prev() : l, // then we remove the layer
  destroy                // first we destroy the previous layer's canvas
)

let l;

document.querySelector("#push").addEventListener("click", () => {
  // each layer creation creates a new list
  l = push(l)
});

document.querySelector("#pop").addEventListener("click", () => {
  // each layer removal creates a new list
  l = pop(l);
});
.fake-canvas{border:3px solid #018bbc;width:75px;height:50px;display:flex;align-items:center;justify-content:center;margin-left:10px;position:relative;}.fake-canvas.disabled{opacity:.5;pointer-events:none}body{height:100vh;display:flex;align-items:center}.fake-canvas.transparent{border-color:#2ecc71;}button{margin:5px;padding:10px;appearance:none;background-color:#018bbc;border:0;color:#fff;text-transform:uppercase}#buttons{display:flex;flex-direction:column;}
<div id="buttons"><button id="push">push</button><button id="pop">pop</button></div>

是的,那个模块作用域的可变变量确实是一种代码味道。一些 OOP 已经可以解决这个问题,但让我们让它发挥作用。堆栈结构很容易变得不可变:

export function createLayer(parent) {
    if (parent)
        parent._canvas.disableInteraction();
    const canvas = createCanvas({ transparent: !!parent });
    return {
        _canvas: canvas,
        stage: canvas.stage,
        destroy() {
            canvas.destroy();
            if (parent) // I think you missed this
                parent._canvas.enableInteraction();
            return parent;
        },
        create() {
            return createLayer(this);
        }
    };
}

它仍然有点奇怪,因为你的 first 层是透明的。也没有表示空堆栈的数据结构。要解决这些问题,我们可以使用

function createLayer(parent, parentCanvas, transparent) {
    parentCanvas.disableInteraction();
    const canvas = createCanvas({ transparent });
    return {
        stage: canvas.stage,
        destroy() {
            canvas.destroy();
            parentCanvas.enableInteraction();
            return parent;
        },
        create() {
            return createLayer(this, canvas, false)
        }
    };
}
export const noLayer = {
    canvas: null,
    destroy() {
        throw new Error("cannot pop the bottom of the layer stack");
    },
    create() {
        return createLayer(this, {
            disableInteraction() {},
            enableInteraction() {}
        }, true);
    }
};

你的图书馆的具有功能意识的用户会写类似

的东西
import { noLayer } from '…';

const a = noLayer.create();
const b = a.create();
const c = b.destroy(); // == a
const d = c.create();
const e = d.create();
const f = e.destroy(); // == d
const g = f.destroy(); // == c == a
const h = g.create();
const i = h.destroy(); // == g == c == a
const j = i.destroy(); // == noLayer