Preact 不会在补液时替换 dom 元素?
Preact isn't replacing dom elements on rehydration?
我正在尝试使用 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 的启发。
这里有两件事,我将一一解释:
1。这里不需要 render() 的第三个参数。
您的 Counter 组件在根部有两个元素(<div>
和 <button>
),将单个 DOM 元素引用作为第三个参数传递给 render 将阻止 Preact使用存在于 "prerendered" DOM.
中的 <button>
默认情况下,render(vdom, parent)
将查看 parent
的所有子项,并确定在附加到现有 DOM 时应重新使用哪些子项。只有一种非常特殊的情况下此行为不起作用并且第三个参数是必要的,即多个 "render roots" 共享同一个父节点。一般来说,最好避免这种情况,这就是为什么第三个参数在文档中并没有真正宣传的原因。
2。 `htm/preact/standalone` 目前似乎已损坏
上周我遇到过类似的问题,所以我知道要检查一下。出于某种原因,当我们将 Preact 捆绑到 HTM 中以创建独立构建时,它破坏了渲染。这可能是过于激进的缩小的结果,应该尽快修复。
同时,可以(有时更好)直接从 unpkg 使用 htm
+ preact
+ preact/hooks
。关键是使用 完全解析的模块 URLs,以便 unpkg 的 ?module
参数将导入转换为您用于手动导入的相同 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
奖励回合:补水
我的脑袋现在正忙着补水 space,所以我对此很感兴趣。根据我的研究,我建议您改变一些方法:
1。使用 JSON 作为数据而不是脚本标签
内联脚本会阻止呈现并强制所有样式表在执行前完全加载。这使得它们非常昂贵,值得不惜一切代价避免。值得庆幸的是,解决方案非常简单:不要使用 <script>__STATE__[1]={..}</script>
进行组件水合作用 data/callsites,而是使用非 JavaScript mimetype 切换到 <script type="..">
。这将使脚本成为非阻塞的,并且你可以在水化时快速轻松地将数据解析为 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() 跳过 diffing
如果您知道您的预渲染 HTML 结构与您的组件将 "boot up" 进入的初始 DOM 树结构完全匹配,hydrate()
让您绕过所有差异,快速启动且无需触摸 DOM。这是更新后的演示,其中 render() 换成了 hydrate() - 没有功能差异,只是性能更好:
https://codesandbox.io/s/thirsty-black-2uci3?file=/index.html:1692-1709
我正在尝试使用 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 的启发。
这里有两件事,我将一一解释:
1。这里不需要 render() 的第三个参数。
您的 Counter 组件在根部有两个元素(<div>
和 <button>
),将单个 DOM 元素引用作为第三个参数传递给 render 将阻止 Preact使用存在于 "prerendered" DOM.
<button>
默认情况下,render(vdom, parent)
将查看 parent
的所有子项,并确定在附加到现有 DOM 时应重新使用哪些子项。只有一种非常特殊的情况下此行为不起作用并且第三个参数是必要的,即多个 "render roots" 共享同一个父节点。一般来说,最好避免这种情况,这就是为什么第三个参数在文档中并没有真正宣传的原因。
2。 `htm/preact/standalone` 目前似乎已损坏
上周我遇到过类似的问题,所以我知道要检查一下。出于某种原因,当我们将 Preact 捆绑到 HTM 中以创建独立构建时,它破坏了渲染。这可能是过于激进的缩小的结果,应该尽快修复。
同时,可以(有时更好)直接从 unpkg 使用 htm
+ preact
+ preact/hooks
。关键是使用 完全解析的模块 URLs,以便 unpkg 的 ?module
参数将导入转换为您用于手动导入的相同 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
奖励回合:补水
我的脑袋现在正忙着补水 space,所以我对此很感兴趣。根据我的研究,我建议您改变一些方法:
1。使用 JSON 作为数据而不是脚本标签
内联脚本会阻止呈现并强制所有样式表在执行前完全加载。这使得它们非常昂贵,值得不惜一切代价避免。值得庆幸的是,解决方案非常简单:不要使用 <script>__STATE__[1]={..}</script>
进行组件水合作用 data/callsites,而是使用非 JavaScript mimetype 切换到 <script type="..">
。这将使脚本成为非阻塞的,并且你可以在水化时快速轻松地将数据解析为 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() 跳过 diffing
如果您知道您的预渲染 HTML 结构与您的组件将 "boot up" 进入的初始 DOM 树结构完全匹配,hydrate()
让您绕过所有差异,快速启动且无需触摸 DOM。这是更新后的演示,其中 render() 换成了 hydrate() - 没有功能差异,只是性能更好:
https://codesandbox.io/s/thirsty-black-2uci3?file=/index.html:1692-1709