Preact не заменяет элементы dom на регидратацию? - PullRequest
0 голосов
/ 09 апреля 2020

Я пытаюсь научиться регидратации DOM, используя преакт. По какой-то неизвестной причине функция render не заменяет исходный узел DOM, а скорее добавляет к нему.

enter image description here

https://github.com/preactjs/preact/issues/24, 3-й параметр render должен предоставить возможность заменить:

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

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

Вопрос: Есть ли какие-либо идеи о том, как я могу обеспечить работу гидратации так, как можно было бы ожидать, например, заменить счетчик stati c на интерактивный?

<!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 репо.

1 Ответ

1 голос
/ 09 апреля 2020

Здесь происходит две вещи, я объясню каждую:

1. Третий аргумент для render () здесь не нужен.

Ваш компонент Counter имеет два элемента в root (<div> и <button>) и передает одну ссылку на элемент DOM в качестве третьего Аргумент рендеринга будет препятствовать тому, чтобы Preact использовал <button>, существующий в «предварительно сгенерированном» DOM.

По умолчанию render(vdom, parent) будет смотреть на всех потомков parent и выяснять, какие они должны быть повторно использованы при подключении к существующему DOM. Существует только один очень конкретный c случай, когда это поведение не работает и третий аргумент оправдан, когда несколько «корней рендеринга» имеют один и тот же parentNode. В общем, лучше избегать такого случая, поэтому этот третий параметр не особо рекламируется в документации.

2. `htm / preact / standalone` в настоящее время, похоже, не работает

На прошлой неделе я сталкивался с подобной проблемой, так что я знал, как это проверить. По какой-то причине, когда мы встроили Preact в HTM для создания автономной сборки, он нарушил рендеринг. Вероятно, это результат чрезмерно агрессивного минимизации и должен быть исправлен в ближайшее время.

Тем временем возможно (а иногда и лучше) использовать htm + preact + preact/hooks непосредственно из unpkg. Ключом является использование полностью разрешенных 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 для данных вместо тегов скрипта

Встроенные скрипты блокируют рендеринг и заставляют все таблицы стилей полностью загружаться перед выполнением. Это делает их непропорционально дорогими и их стоит избегать любой ценой. К счастью, решение довольно простое: вместо использования <script>__STATE__[1]={..}</script> для данных о гидратации компонентов / сайтов вызовов переключитесь на <script type=".."> с типом mime, отличным от JavaScript. Это сделает скрипт неблокирующим, и вы сможете быстро и легко проанализировать данные как 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>

Обратите внимание, что теперь вы можете использовать расположение этого тега скрипта внутри вашего компонента данных root, чтобы связать его с компонентом без необходимости использования глобальных идентификаторов.

Вот пример исправленной версии вашей демонстрации с указанным выше изменением: https://codesandbox.io/s/quirky-wildflower-29944?file= / index. html: 202-409

Надеюсь, вы найдете Обновлен корень / данные / рендер l oop для улучшения.

2. Используйте hydrate (), чтобы пропустить diff

Если вы знаете, что предварительно отрендеренная структура HTML точно соответствует исходной структуре дерева DOM, в которую ваши компоненты будут загружаться, hydrate() позволяет вам обойти все диффинг, быстро загружаться и не касаясь DOM. Вот обновленное демо с render (), замененным на hydrate () - функциональной разницы нет, просто у него будет лучшая производительность:

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

...