【问题标题】:rendering to the document nested dom nodes渲染到文档嵌套的 dom 节点
【发布时间】:2025-12-11 23:50:01
【问题描述】:

我一直在研究如何将 html 渲染到文档中,主要是为了好玩和学习。我看到了如何反应,并且大多数 vdom/hyperscript 实现都递归地呈现嵌套节点。想知道如何使用数组实现类似的功能。

所以,如果我有一个返回渲染方法的函数,它会输出一个数组。

    function Comp() {
        return {
          render: function () {
            return [
              "<div>",
              '  <button name="button-test" type="button">test</button>',
              SubComp(),
              "</div>",
            ];
          },
          postRender: function (dom) {
            dom
              .querySelector("button[name=button-test]")
              .addEventListener("click", function () {
                console.log("haha");
              });
          },
        };
      }


      function SubComp() {
    return {
      render: function () {
        return [
          "<div>",
          '  <button name="button-does-nothing" type="button">nothing</button>',
          "</div>",
        ];
      },
    };
  }

在上面的示例中,我可以忽略 postrender 方法,从 Comp 内部调用 SubComp().render() 方法并执行以下操作:

 document.getElementById('someid').innerHTML(Comp().render().flat(Infinity).join(''));

然后在执行任何与 dom 相关的内容之后。这将嵌套我放入的任何内容。但是,如果我想专门为该节点或 html 的一部分附加 dom 事件,我也会失去处理每个函数的能力。

所以我的问题是,有没有人知道我如何通过为每个函数提供可用的 dom 节点来实现这一点。然后我可以执行它的 postrender 方法(或当节点准备好时我想要使用的 wtv),然后才将它嵌套到它可能具有的外部节点中。

这更像是一个练习和学习,所以请尽量不要向我推荐库或框架。

PS:如果有人知道怎么做,在运行时将返回的数组转换为超脚本对象也对我有用。

非常感谢。

【问题讨论】:

    标签: javascript arrays recursion


    【解决方案1】:

    这是我想出来的。

    我认为这可以通过减少来改善。 这是我目前走了多远,没有做太多测试,但似乎有效。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <script>
        function render(component) {
      if (component.preRender) {
        component.preRender();
      }
      var thisComponent = component.render();
      var thisDom = document.createElement('div');
      var children = [];
      thisComponent.forEach(function (c, index) {
        if (typeof c === 'object' && c.hasOwnProperty('render')) {
          children.push(render(c, thisDom.querySelector('div')));
          thisComponent[index] = '<div id="child' + (children.length) + '"></div>';
        }
      });
      var parsedDom = new DOMParser().parseFromString(thisComponent.flat(Infinity).join(''), 'text/html').body.childNodes;
      var length = parsedDom.length;
      for (var i = 0; i < length; i += 1) {
        thisDom.appendChild(parsedDom[0]);
      }
      if (component.postRender) {
        component.postRender(thisDom);
      }
      children.forEach(function (child, index) {
        while (child.firstChild) {
          var tchild = child.removeChild(child.firstChild);
          thisDom.querySelector('[id=child' + (index + 1) + ']').parentElement.insertBefore(tchild, thisDom.querySelector('[id=child' + (index + 1) + ']'));
        }
        thisDom.querySelector('[id=child' + (index + 1) + ']').remove();
      });
      return thisDom;
    }
        </script>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app"></div>
      </body>
    </html>
    <script>
      function Component() {
        return {
          render: function () {
            return [
              '  <button name="button-test" type="button">test click me</button>',
              '<div id="test">',
              SubComponent(),
              "</div>",
            ];
          },
          postRender: function (dom) {
            dom
              .querySelector("button[name=button-test]")
              .addEventListener("click", function () {
                console.log("this was added while detached haha");
              });
          },
        };
      }
      function SubComponent() {
        return {
          render: function () {
            return [
              '  <button name="button-does-nothing" type="button">nothing</button>',
              SubSubComp(),
            ];
          },
        };
      }
      function SubSubComp() {
        return {
          render: function () {
            return [
              '  <button name="button-does-nothing-too" type="button">does nothing as well</button>',
            ];
          },
        };
      }
      document.getElementById("app").appendChild(render(Component()));
    </script>

    因此,如果有人可以帮助改进代码,我也会接受作为答案。

    另外一件事,你们知道操纵 detached dom 的速度有多快吗?还是有这方面的基准?我发现只有 dom 附加到浏览器。

    【讨论】:

    • 您可以看到我正在创建一个引用作为 id'ed div,因此我可以稍后将其替换为子生成的 dom,然后再将其附加到文档。不想那样,只是想让它工作,然后我可以稍后解决,或者我们这里的一些人可以建议 sg 更优雅。