【问题标题】:Preact isn't replacing dom elements on rehydration?Preact 不会在补液中替换 dom 元素?
【发布时间】:2020-04-09 04:20:28
【问题描述】:

我正在尝试使用 preact 学习 dom 的补液。由于某些未知原因,render 函数并未替换原始 DOM 节点,而是附加到它。

https://github.com/preactjs/preact/issues/24render 的第三个参数应该提供替换的机会:

render(<App />, into, into.lastChild);

https://codesandbox.io/s/beautiful-leavitt-rkwlw?file=/index.html:0-1842

问题:关于如何确保补水发挥预期作用的任何想法,例如用交互式计数器替换静态计数器?

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Test</title>
  </head>
  <body>
    <script>
      window.__STATE__ = { components: {} };
    </script>
    <main>
      <div>
        <script data-cmp-id="1">
          window.__STATE__.components[1] = {
            name: "Counter",
            props: { id: 1 }
          };
        </script>
        <div>HOW MANY LIKES 0</div>
        <button>Increment</button>
      </div>
    </main>
    <script type="module">
      import {
        html,
        useState,
        render
      } from "https://unpkg.com/htm/preact/standalone.module.js";
      let id = 0;

      export const withHydration = Component => props => {
        id += 1;
        return html`
          <${Component} ...${props} />
        `;
      };

      const Counter = () => {
        const [likes, setLikes] = useState(0);
        const handleClick = e => {
          e.preventDefault();
          setLikes(likes + 1);
        };

        return html`
          <div>HOW MANY LIKES ${likes}</div>
          <button onClick=${handleClick}>Increment</button>
        `;
      };

      const componentMap = {
        Counter: withHydration(Counter)
      };

      const $componentMarkers = document.querySelectorAll(`[data-cmp-id]`);

      Array.from($componentMarkers).forEach($marker => {
        debugger;
        const $component = $marker.nextElementSibling;
        const { name, props } = window.__STATE__.components[
          $marker.dataset.cmpId
        ];
        const Component = componentMap[name];

        render(
          html`
            <${Component} ...${props} />
          `,
          $component.parentNode,
          $component
        );
      });
    </script>
  </body>
</html>

所有这些都受到https://github.com/maoberlehner/eleventy-preact repo 的启发。

【问题讨论】:

    标签: javascript react-hooks preact hydration


    【解决方案1】:

    这里发生了两件事,我将分别解释:

    1。此处不应需要 render() 的第三个参数。

    您的 Counter 组件在根有两个元素(&lt;div&gt;&lt;button&gt;),并且将单个 DOM 元素引用作为第三个参数传递给 render 将阻止 Preact 使用存在于“预渲染”DOM。

    默认情况下,render(vdom, parent) 将查看 parent 的所有子代,并确定在附加到现有 DOM 时应该重用哪些子代。只有一种非常特殊的情况下这种行为不起作用,并且第三个参数是有保证的,即多个“渲染根”共享同一个父节点时。一般来说,最好避免这种情况,这就是为什么文档中没有真正宣传第三个参数的原因。

    2。 `htm/preact/standalone` 目前似乎已损坏

    上周我遇到了类似的问题,所以我知道要检查一下。出于某种原因,当我们将 Preact 捆绑到 HTM 以创建独立构建时,它破坏了渲染。这可能是由于过于激进的缩小造成的,应该尽快修复。

    与此同时,可以(有时更好)直接从 unpkg 使用 htm + preact + preact/hooks。关键是使用完全解析的模块 URL,以便 unpkg 的 ?module 参数将导入转换为您用于手动导入的相同 URL。以下是演示的正确 URL:

    import htm from "https://unpkg.com/htm@latest?module";
    import { h, render } from "https://unpkg.com/preact@latest?module";
    import { useState } from "https://unpkg.com/preact@latest/hooks/dist/hooks.module.js?module";
    

    删除第三个渲染参数并换掉这些导入后,您的演示实际上可以正常工作:?

    https://codesandbox.io/s/fast-fire-dyzhg?file=/index.html:719-954


    奖励回合:补水

    我现在非常关注补水空间,所以这对我很感兴趣。根据我的研究,我建议您改变一些方法:

    1。对数据使用 JSON 而不是脚本标签

    内联脚本会阻止渲染并强制在执行之前完全加载所有样式表。这使得它们异常昂贵,值得不惜一切代价避免。值得庆幸的是,解决方案非常简单:不要将 &lt;script&gt;__STATE__[1]={..}&lt;/script&gt; 用于组件水合数据/调用站点,而是使用非 JavaScript mimetype 切换到 &lt;script type=".."&gt;。这将使脚本非阻塞,并且您可以在水合时快速轻松地将数据解析为 JSON - 比评估 JS 快得多,并且您可以控制它何时发生。看起来是这样的:

    <div data-component="Counter">
        <div>HOW MANY LIKES 0</div>
        <button>Increment</button>
        <script type="text/hydration-data">
            {"props":{"id":1}}
        </script>
    </div>
    

    请注意,您现在可以使用该脚本标记在数据组件根目录中的位置将其与组件关联起来,而无需全局 ID。

    这是您的演示的固定版本的一个分支,具有上述更改: https://codesandbox.io/s/quirky-wildflower-29944?file=/index.html:202-409

    我希望您发现 updated root/data/render loop 是一种改进。

    2。使用 hydrate() 跳过差异

    如果您知道您的预渲染 HTML 结构与您的组件将要“启动”到的初始 DOM 树结构完全匹配,hydrate() 可以让您绕过所有差异,快速启动而无需接触 DOM。这是将 render() 替换为 hydrate() 的更新演示 - 没有功能差异,只是性能更好:

    https://codesandbox.io/s/thirsty-black-2uci3?file=/index.html:1692-1709

    【讨论】:

    • 我一辈子都想不通id 在这一切中做了什么。太混乱了。
    • id 什么也没做,只是一些示例数据。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-22
    • 2010-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-06
    相关资源
    最近更新 更多