在SolidJS中,信号(Signals)如何被重复渲染到DOM中?

huangapple go评论58阅读模式
英文:

In SolidJS, how do Signals get repeatedly rendered into the DOM?

问题

我认为我在响应性方面对信号、反应和导数有很好的理解。

然而,我不明白的是,怎么突然间,把它们放进一个App()函数中,当信号更新时,信号会被重复地更新到DOM中?

我认为可能这个文档解释了这个问题,但是似乎太复杂了。能不能用几段话来解释一下?

例如,我会想象:

<div>{value()}</div>

其中value()来自于:

const [value, setValue] = createSignal(123);

会有一些代码来创建一个反应,以对value信号的更改作出反应并更新DOM,但是这是怎么发生的呢?这个createSignal()是和常规的createSignal()一样的吗,还是说它是一个特殊版本,设置了一个反应来更新DOM?另外一个相关的问题是,由于App()只运行一次,那么为什么render()需要接受() => <App />而不能只接受<App />作为它的第一个参数呢?如果App()只运行一次,那么() => <App />也只运行一次。那么为什么不直接给一个<App />呢?

英文:

I think I understand Signals, Reactions, Derivations quite well in terms of Reactivity.

However, I can't understand how all of a sudden, putting them into an App() function and the Signals can be updated into the DOM repeated when the Signals are updated?

I think perhaps this doc explains it, but it seems too complicated to understand. Can it be explained in a couples of paragraphs?

For example, I'd imagine

&lt;div&gt;{value()}&lt;/div&gt;

where value() comes from

const [value, setValue] = createSignal(123);

would have some code to have some Reaction that reacts to the Signal value's change and update the DOM, but how did it happen? Is this createSignal() the same as the regular createSignal() or is it a special edition that sets up a Reaction to update the DOM? Also related is, since App() is only run once, then how come the render() needs to take () =&gt; &lt;App /&gt; but cannot just take &lt;App /&gt; as its first argument? If App() runs once only, that means () =&gt; &lt;App /&gt; runs once only. So then why not just simply give a &lt;App /&gt;?

答案1

得分: 3

组件被编译成效果,当信号的值更新时重新评估。让我简要解释一下:

  1. 调用 createSignal 时,会创建一个订阅者列表并返回一对函数:一个设置器和一个获取器。每个信号都保留其自己的订阅者列表。

  2. 设置器函数设置下一个值,而获取器函数返回存储的值。

  3. 调用获取器函数时,获取器调用包装的效果将被添加到信号的订阅者列表之前获取值。因此,读取一个值,换句话说调用获取器如 value(),会自动将效果插入到信号的订阅者列表中。

这个效果可以通过手动创建 createEffect 或者在 JSX 组件编译为函数调用时自动创建。

所以,当信号更新时,执行的就是这些效果。

  1. JSX 组件被编译成效果,每当信号值更改时,它会创建并更新 DOM 节点。

看看组件如何编译成效果:

import { createSignal } from 'solid-js';

export const Counter = () => {
  const [count, setCount] = createSignal(0);

  return <div>{count()}</div>;
};

上述组件的编译输出如下:

import { createSignal } from 'solid-js';

import { template as _$template } from "solid-js/web";
import { insert as _$insert } from "solid-js/web";

const _tmpl$ = /*#__PURE__*/_$template(`<div></div>`, 2);

export const Counter = () => {
  const [count, setCount] = createSignal(0);
  return (() => {
    const _el$ = _tmpl$.cloneNode(true);
    _$insert(_el$, count);
    return _el$;
  })();
};

看到这一行 _$template(

, 2)_tmpl$.cloneNode(true) 以及 _$insert(_el$, count);

在这里:

  • _$template 函数创建反映组件 DOM 结构的模板。

  • cloneNode 克隆模板。

  • $insert 函数填充值并将返回的 DOM 元素插入其父 DOM 元素。

所以,更新 DOM 不是通过获取器的调用,而是重新运行整个效果,这个效果包含了获取器的调用。

为什么 render() 需要以 () => 的形式传递,而不能直接以 作为第一个参数?

文档回答了你的问题。Solid 运行时跟踪谁拥有谁,以防止内存泄漏。传递一个函数是为了设置事务。

很重要的一点是第一个参数必须是一个函数:不要直接传递 JSX(如在 render(, ...) 中),因为这会在 render 能够设置一个用于跟踪 App 内部信号依赖关系的根之前调用 App。 https://www.solidjs.com/docs/latest#render

所以,你可以将 <App /> 视为函数调用,更准确地说是对 App 函数的调用。因此,你将向 render 函数提供一个值。

如果你不喜欢 () => <App /> 语法,你可以像这样传递 App 函数:

const dispose = render(App, document.getElementById("app"));

重要的是,你提供一个返回 JSX 元素的函数,这将用作应用程序的根元素。

英文:

Components are compiled into effects that gets re-evaluated when the signal's value is updated. Let me explain it briefly:

  1. When invoked createSignal creates a subscribers list and returns a pair of functions: a setter and a getter. Every signal keeps its own subscribers list.

The setter function sets the next value, while the getter function returns the stored value.

  1. When invoked, the getter function gets the stored value but before getting the value, the effect that wraps the getter invocation will be added to the signal's subscriber list.

So, reading a value, in other words invoking the getter as value(), automatically inserts an effect into the signal's subscribers list.

This effect can be created via createEffect maunally or automatically when JSX component gets compiled into a function call.

So, when the signal gets updated, it is these effects that gets executed.

  1. JSX components are compiled into effects, which creates and updates DOM nodes whenever signal value changes.

Lets see how components gets compiled into effects:

import { createSignal } from &#39;solid-js&#39;;

export const Counter = () =&gt; {
  const [count, setCount] = createSignal(0);

  return &lt;div&gt;{count()}&lt;/div&gt;
};

The compiled output for the above component is as follows:

import { createSignal } from &#39;solid-js&#39;;

import { template as _$template } from &quot;solid-js/web&quot;;
import { insert as _$insert } from &quot;solid-js/web&quot;;

const _tmpl$ = /*#__PURE__*/_$template(`&lt;div&gt;&lt;/div&gt;`, 2);

export const Counter = () =&gt; {
  const [count, setCount] = createSignal(0);
  return (() =&gt; {
    const _el$ = _tmpl$.cloneNode(true);
    _$insert(_el$, count);
    return _el$;
  })();
};

See the line _$template(`&lt;div&gt;&lt;/div&gt;`, 2) and _tmpl$.cloneNode(true) and _$insert(_el$, count);.

Here:

  • _$template function creates a template reflecting the component's DOM structure.

  • cloneNode clones the template.

  • $insert function populates the values and inserts the returned DOM element into its parent DOM element.

So, it is not the getter's invocation but the re-running the whole effect that has the getter invocation updates the DOM.

> How come the render() needs to take () => <App /> but cannot just take <App /> as its first argument?

The documentation answers your question. Solid's runtime keeps tracks of who owns the whom in order to prevent memory leaks. Passing a function is needed for settings things up.

> It's important that the first argument is a function: do not pass JSX directly (as in render(<App/>, ...)), because this will call App before render can set up a root to track signal dependencies within App. https://www.solidjs.com/docs/latest#render

So, you can see &lt;App /&gt; as a function invocation, or more accurately as the invocation of the App function. So, you will be feeding a value to the render function.

If you don't like () =&gt; &lt;App /&gt; syntax, you can pass App function like so:

const dispose = render(App, document.getElementById(&quot;app&quot;));

The important thing is you provide a function that returns a JSX element which will be used as the root element of your application.

huangapple
  • 本文由 发表于 2023年3月7日 11:21:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/75657763.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定