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 的不透明度低于启用的交互。
你在这里基本上拥有的是一种带有一些状态封装的堆栈形式。
您关心的是:
- 将项目推到顶部(添加新层)
- 从顶部弹出一个项目(破坏一层)
- 跟踪当前阶段
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
是否有更好或更“实用”的方式来组织此代码?
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 的不透明度低于启用的交互。
你在这里基本上拥有的是一种带有一些状态封装的堆栈形式。
您关心的是:
- 将项目推到顶部(添加新层)
- 从顶部弹出一个项目(破坏一层)
- 跟踪当前阶段
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