0

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

在此处输入图像描述

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

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 的启发。

4

1 回答 1

1

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

1. 这里不需要 render() 的第三个参数。

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

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

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

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

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

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 代替脚本标签

内联脚本会阻止渲染并强制在执行之前完全加载所有样式表。这使得它们异常昂贵,值得不惜一切代价避免。值得庆幸的是,解决方案非常简单:不要使用<script>__STATE__[1]={..}</script>组件水合数据/调用站点,而是使用<script type="..">非 JavaScript 的 mimetype。这将使脚本非阻塞,并且您可以在水合时快速轻松地将数据解析为 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

我希望您发现更新的根/数据/渲染循环是一种改进。

2. 使用 hydrate() 跳过差异

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

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

于 2020-04-09T14:50:35.510 回答