render 源码分析

https://www.solidjs.com/docs/latest/api#render

render 函数是 web 应用的入口。

import { render } from "solid-js/web";
import type { JSX, MountableElement } from "solid-js/web";

function render(code: () => JSX.Element, element: MountableElement): () => void;
typescript

第一个参数可以函数组件声明或者使用函数返回组件,第二个参数是需要被挂载的元素。

这个元素推荐使用一个空元素,如果调用 render 返回 dispose 函数,会清理掉所有子元素,包括已经存在的静态内容。

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

我们将以下面这个例子对源码进行分析:

function HelloWorld() {
  const [count, setCount] = createSignal(0)

  setTimeout(() => {
    setCount(count() + 1)
  }, 1000);

  return html`<div >${ count }</div>`
}

render(HelloWorld, document.getElementById('app'))
typescript

首次渲染

render

render

render 函数没有定义在在 solid 库中,而是被封装在 dom-expressions 中。

dom-expressions 是 Ryan Carniato 针对于响应式开发的一个库,它可以进行细粒度的检测。

export function render(code, element, init) {
  let disposer;
  root(dispose => {
    disposer = dispose;
    element === document
      ? code()
      : insert(element, code(), element.firstChild ? null : undefined, init);
  });
  return () => {
    disposer();
    element.textContent = "";
  };
}
typescript

render 函数可以接收三个参数,分别是 code、element、init。

  • code:即我们传入的代码,可以是一个组件,可以是一个函数;
  • element:DOM 元素,会将 code 渲染后的结果追加到 element 元素内部;
  • init:初始值,可以是函数也可以是数组

首先定义 disposer 变量,然后调用 root 函数,函数内部首先将参数 dispose 赋值给 disposer,然后会对 element 进行判断:

  • 如果 element 等于 document,直接执行 code 即可;
  • 否则会调用 insert 方法,对 code() 返回的结果进行插入

最后在返回一个函数,内部调用 disposer,并将元素的内容清空。

代码逻辑其实比较清晰,不过有一点可需要说明一下,这里的 root 其实就是 createRoot 函数。

import { root, effect, memo, getOwner, createComponent, sharedConfig, untrack } from "rxcore";
typescript

可以看到 root 是从 rxcore 中导入的,初看可能很懵逼,因为在 package.json 文件中并没有安装这个库。

其实这个库指向的是 solid 的 web 目录 src 文件夹下的 core.ts 文件。

dom-expression 中的 babel.config.js 中使用 babel-plugin-transfrom-rename-import 插件,对变量进行了替换。

module.exports = {
  presets: [["@babel/preset-env", { targets: { node: "current" } }]],
  plugins: [
    [
      "babel-plugin-transform-rename-import",
      {
        original: "rxcore",
        replacement: __dirname + "/core"
      }
    ],
    ["babel-plugin-jsx-dom-expressions", { moduleName: "./custom", generate: "universal" }]
  ]
};
typescript

可以看到,插件将 rxcore 字符串替换为当前目录下的 core 文件。

//@ts-nocheck
import {
  createRoot,
  createRenderEffect,
  createMemo,
  createComponent,
  getOwner,
  sharedConfig,
  untrack
} from "solid-js";

// reactive injection for dom-expressions
function memo<T>(fn: () => T, equals: boolean) {
  return createMemo(fn, undefined, !equals ? { equals } : undefined);
}

export {
  getOwner,
  createComponent,
  createRoot as root,
  createRenderEffect as effect,
  memo,
  sharedConfig,
  untrack
};
typescript

可以看到这里的 root 其实就是 createRoot,createRoot 我们之前也分析过。

createRoot

export function render(code, element, init) {
  let disposer;
  root(dispose => {
    disposer = dispose;
    element === document
      ? code()
      : insert(element, code(), element.firstChild ? null : undefined, init);
  });
  return () => {
    disposer();
    element.textContent = "";
  };
}
typescript

这段代码首先会调用 root 函数,将匿名函数作为参数传入,第二个参数为 undefined。

export function createRoot<T>(fn: RootFunction<T>, detachedOwner?: Owner): T {
  const listener = Listener,
    owner = Owner,
    unowned = fn.length === 0,
    root: Owner =
      unowned && !"_SOLID_DEV_"
        ? UNOWNED
        : { owned: null, cleanups: null, context: null, owner: detachedOwner || owner },
    updateFn = unowned
      ? "_SOLID_DEV_"
        ? () =>
            fn(() => {
              throw new Error("Dispose method must be an explicit argument to createRoot function");
            })
        : fn
      : () => fn(() => untrack(() => cleanNode(root)));

  if ("_SOLID_DEV_") {
    if (owner) root.name = `${owner.name}-r${rootCount++}`;
    globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root);
  }

  Owner = root;
  Listener = null;

  try {
    return runUpdates(updateFn as () => T, true)!;
  } finally {
    Listener = listener;
    Owner = owner;
  }
}
typescript

这里的 Listener 和 Owner 都为空。

我们传递的函数参数个数并不为 0 , 所以这时的 unowned 变量为 false,updateFn 为:

() => fn(() => untrack(() => cleanNode(root)))
typescript

然后将 Owener 赋值为 root,Listerner 赋值为 null。调用 runUpdates 函数,传入 updateFn 作为参数。

最后函数执行完毕,将 Listerner 和 Owener 重置。

runUpdates

function runUpdates<T>(fn: () => T, init: boolean) {
  if (Updates) return fn();
  let wait = false;
  if (!init) Updates = [];
  if (Effects) wait = true;
  else Effects = [];
  ExecCount++;
  try {
    const res = fn();
    completeUpdates(wait);
    return res;
  } catch (err) {
    if (!Updates) Effects = null;
    handleError(err);
  }
}
typescript

这个函数我们已经分析过很多次,主要就是初始化 Effects 数组,然后执行我们传入的回调函数,最后调用 completeUpdate 完成更新。

anonymous(匿名函数)

runUpdates 中执行的 fn 其实就是在 render 函数中传入的匿名函数。

export function render(code, element, init) {
  let disposer;
  root(dispose => {
    disposer = dispose;
    element === document
      ? code()
      : insert(element, code(), element.firstChild ? null : undefined, init);
  });
  return () => {
    disposer();
    element.textContent = "";
  };
}
typescript

dispose 也是一个匿名函数,即 fn 内部的匿名函数。

() => fn(() => untrack(() => cleanNode(root)))

// =>

() => untrack(() => cleanNode(root))
typescript

这里会将这个匿名函数赋值给 disposer,主要的作用就是对 root 进行清理。

然后会调用 insert方法,这里其实会先执行 code 函数,即我们传入的函数。

function HelloWorld() {
  const [count, setCount] = createSignal(0)

  setTimeout(() => {
    setCount(count() + 1)
  }, 1000);

  return html`<div>${ count }</div>`
}
typescript

首先会定义 signalState,value 值为 0,并返回 getter 和 setter 函数。其次定义一个定时器函数,内部首先使用 getter 获取值,然后再利用 setter 进行更新。最后将我们的代码返回。

因为这里是在浏览中直接引入调试的,不能直接在函数中使用 html 标签,所以这里使用 html 标签函数对 html 标签字符串进行解析。

我们可以举个简单的例子,定义一个 html 标签函数,将其放在 html 标签字符串前面,浏览器就会自动调用 html 函数,对标签字符串进行解析,并将解析后的内容以参数的形式传递给我们自定义的 html 函数。

第一个参数是包含一个字符串值得数组,其余的参数与表达式相关。

更多标签函数内容,可以查看 MDN 关于 模板字符串 的文章。

const count = 1

function html2(static, ...args) {
console.log(static, args)
}

html2`<div>${ count }</div>`
typescript

html

html 函数同样定义在 dom-expression 库中。

const cache = new Map<TemplateStringsArray, HTMLTemplateElement[]>();

export function createHTML(r: Runtime, { delegateEvents = true } = {}): HTMLTag {
  let uuid = 1;
  (r as any).wrapProps = (props: any) => {
    const d = Object.getOwnPropertyDescriptors(props);
    for (const k in d) {
      if (typeof d[k].value === "function" && !d[k].value.length) r.dynamicProperty(props, k);
    }
    return props;
  };  
  
  function html(statics: TemplateStringsArray, ...args: unknown[]): Node {
    const templates = cache.get(statics) || createTemplate(statics);
    return (templates[0] as CreateableTemplate).create(templates, args, r);
  }
  
  return html
}
typescript

在 html 函数中,首先会从 cache 寻找 statics,如果存在用 cache 中数据,否则调用 createTemplate 方法创建模板。最后调用 templates 第一个元素的 create 方法,并将结果返回。

运行我们的测试代码中,这里的 static 和 args 分别是:

createTemplate

createTemplate 顾名思义,就是用来创建模板。

它会解析我们的 statics,最终返回一个 templates 数组,并向第一个元素身上挂载一个 create 函数,供外部调用这个函数。

function createTemplate(statics: TemplateStringsArray) {
  let i = 0,
    markup = "";
  for (; i < statics.length - 1; i++) {
    markup = markup + statics[i] + "<!--#-->";
  }
  markup = markup + statics[i];
  markup = markup
    .replace(selfClosing, fullClosing)
    .replace(/<(<!--#-->)/g, "<#")
    .replace(/\.\.\.(<!--#-->)/g, "#")
    .replace(attrSeeker, attrReplacer)
    .replace(/>\n+\s*/g, ">")
    .replace(/\n+\s*</g, "<")
    .replace(/\s+</g, " <")
    .replace(/>\s+/g, "> ");

  const [html, code] = parseTemplate(parse(markup)),
    templates: HTMLTemplateElement[] = [];

  for (let i = 0; i < html.length; i++) {
    templates.push(document.createElement("template"));
    templates[i].innerHTML = html[i];
    const nomarkers = templates[i].content.querySelectorAll("script,style");
    for (let j = 0; j < nomarkers.length; j++) {
      const d = (nomarkers[j].firstChild as Text)!.data || "";
      if (d.indexOf(marker) > -1) {
        const parts = d.split(marker).reduce((memo, p, i) => {
          i && memo.push("");
          memo.push(p);
          return memo;
        }, [] as string[]);
        nomarkers[i].firstChild!.replaceWith(...parts);
      }
    }
  }
  (templates[0] as CreateableTemplate).create = code;
  cache.set(statics, templates);
  return templates;
}
typescript

create 函数即 parseTemplate 函数生成并返回的 code 函数,它是一个匿名函数:

function parseTemplate(nodes: IDom[]): [string[], TemplateCreate] {
	// ...
  return [
    templateNodes.map(t => stringify(t)),
    new Function(
      "tmpls",
      "exprs",
      "r",
      options.decl.join(",\n") +
        ";\n" +
        options.exprs.join(";\n") +
        (toplevel ? "" : `;\nreturn _$el${id};\n`)
    ) as TemplateCreate
  ];
}
typescript

具体的创建模板的逻辑这里先不去深究,感兴趣的可以自己会调试下相关代码。

insert (inner)

function html(statics: TemplateStringsArray, ...args: unknown[]): Node {
  const templates = cache.get(statics) || createTemplate(statics);
  return (templates[0] as CreateableTemplate).create(templates, args, r);
}
typescript

执行完 createTemplate 方法中,会在第一个元素上挂载 create 方法,即 parseTemplate 返回的 code 函数。

在这个例子中,我们的 code 函数是这样:

(function anonymous(tmpls,exprs,r
) {
  const _$el1 = tmpls[0].content.firstChild.cloneNode(true),
  _$el2 = _$el1.firstChild;
  r.insert(_$el1, exprs[0]);
  return _$el1;
})
typescript

这段代码最关键的逻辑就是执行了一次 insert 方法。

export function insert(parent, accessor, marker, initial) {
  if (marker !== undefined && !initial) initial = [];
  if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
  effect(current => insertExpression(parent, accessor(), current, marker), initial);
}
typescript

因为此时我们传入的第二个参数并不是函数,所以会执行 effect 方法,这里就非常关键,effect 函数内部会创建一个 computation 对象,在这个过程中,state 就和 computation 完成了关联,即模板和数据完成了关联,这样后续我们设置值的时候才会重新触发渲染。

接下来我们就来深入分析下这段逻辑。

createRenderEffect(inner)

注意,这里的 effect 是 createRenderEffect,并不是 createEffect。

export function createRenderEffect<Next, Init>(
  fn: EffectFunction<Init | Next, Next>,
  value?: Init,
  options?: EffectOptions
): void {
  const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined);
  if (Scheduler && Transition && Transition.running) Updates!.push(c);
  else updateComputation(c);
}
typescript

首先创建一个 computation 对象。然后判断 Scheduler、Transition 是否存在,且 Transition 是否正在运行。

如果命中条件,将 computation 添加到 Updates 数组中,否则执行 updateComputation 方法。

这里的 scheduler 和 Transition 都是空,所以会执行 updateComputation 方法。

updateComputation(inner)

function updateComputation(node: Computation<any>) {
  if (!node.fn) return;
  cleanNode(node);
  const owner = Owner,
    listener = Listener,
    time = ExecCount;
  Listener = Owner = node;
  runComputation(
    node,
    Transition && Transition.running && Transition.sources.has(node as Memo<any>)
      ? (node as Memo<any>).tValue
      : node.value,
    time
  );

  if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
    queueMicrotask(() => {
      runUpdates(() => {
        Transition && (Transition.running = true);
        runComputation(node, (node as Memo<any>).tValue, time);
      }, false);
    });
  }
  Listener = listener;
  Owner = owner;
}
typescript

这个方法我们分析过很多次,首先会缓存旧的 Listener、Owner,然后执行 runComputation 方法,最后再恢复变量值。

runComputation(inner)

function runComputation(node: Computation<any>, value: any, time: number) {
  let nextValue;
  try {
    nextValue = node.fn(value);
  } catch (err) {
    if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
    handleError(err);
  }
  if (!node.updatedAt || node.updatedAt <= time) {
    if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
      writeSignal(node as Memo<any>, nextValue, true);
    } else if (Transition && Transition.running && node.pure) {
      Transition.sources.add(node as Memo<any>);
      (node as Memo<any>).tValue = nextValue;
    } else node.value = nextValue;
    node.updatedAt = time;
  }
}
typescript

runComputation 最重要的就是执行 node.fn 方法:

effect(current => insertExpression(parent, accessor(), current, marker), initial);
typescript

所以,这里会执行 insertExpression 方法,并传入相关参数。

readSignal(inner)

执行 insertExpression 函数前,会先执行 accessor 函数,这里的 accessor 其实就是 readSignal ,用来返回当前 state 的值。

readSignal 中建立起 computation 和 state 之间的关系,这样我们在设置值的时候才能得到依赖的 computation ,从而更新视图。

// Internal
export function readSignal(this: SignalState<any> | Memo<any>) {
	// ...
  if (Listener) {
    const sSlot = this.observers ? this.observers.length : 0;
    if (!Listener.sources) {
      Listener.sources = [this];
      Listener.sourceSlots = [sSlot];
    } else {
      Listener.sources.push(this);
      Listener.sourceSlots!.push(sSlot);
    }
    if (!this.observers) {
      this.observers = [Listener];
      this.observerSlots = [Listener.sources.length - 1];
    } else {
      this.observers.push(Listener);
      this.observerSlots!.push(Listener.sources.length - 1);
    }
  }
  if (runningTransition && Transition!.sources.has(this)) return this.tValue;
  return this.value;
}
typescript

这里的 Listener 就是调用 createRenderEffect 方法时创建的 computation 对象,在 updateComputation 时将 computation 赋值给全局 Listener。

最后将 state 的值返回。

insertExpression(inner)

effect(current => insertExpression(parent, accessor(), current, marker), initial);
typescript

然后就执行到 insertExpression 方法。

这里的 parent 即 div 元素,value 是当前 state 的值为 0,current、markder、unwrapArray 都不存在。

function insertExpression(parent, value, current, marker, unwrapArray) {
  if (sharedConfig.context && !current) current = [...parent.childNodes];
  while (typeof current === "function") current = current();
  if (value === current) return current;
  const t = typeof value,
    multi = marker !== undefined;
  parent = (multi && current[0] && current[0].parentNode) || parent;

  if (t === "string" || t === "number") {
    if (sharedConfig.context) return current;
    if (t === "number") value = value.toString();
    if (multi) {
      let node = current[0];
      if (node && node.nodeType === 3) {
        node.data = value;
      } else node = document.createTextNode(value);
      current = cleanChildren(parent, current, marker, node);
    } else {
      if (current !== "" && typeof current === "string") {
        current = parent.firstChild.data = value;
      } else current = parent.textContent = value;
    }
  } else if (value == null || t === "boolean") {
		// ...
  } else if (t === "function") {
    // ..
  } else if (Array.isArray(value)) {
    // ...
  } else if (value instanceof Node) {
   	// ...
  } else if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, value);

  return current;
}
typescript

因为这里的 t 是 number 类型,我们暂时只需要关注 t === 'string' || t === 'number' 这段分支逻辑。

首先判断 sharedConfig.context 是否存在,如果存在,直接返回 current,这里当然是不存在的。

因为 t 是 number 类型,首先会将 value 转换为字符串类型。然后判断 multi,这里也是 false,所以会执行下面这段代码。

if (current !== "" && typeof current === "string") {
  current = parent.firstChild.data = value;
} else current = parent.textContent = value;
typescript

current 也不存在,这里会为 current 和 parent.textContent 赋值为当前的 value,然后将 current 返回。

当 insertExpression 执行完毕,已经为 div 元素设置文本内容,内容为 value 值,字符串 0。

insert (outer)

执行完 html 函数之后,回到 render 函数。

我们在 insert 的时候调用了 code 触发后面一系列逻辑,现在我们已经获取到返回结果。

export function render(code, element, init) {
  let disposer;
  root(dispose => {
    disposer = dispose;
    element === document
      ? code()
      : insert(element, code(), element.firstChild ? null : undefined, init);
  });
  return () => {
    disposer();
    element.textContent = "";
  };
}
typescript

接下来调用 insert 函数。

export function insert(parent, accessor, marker, initial) {
  if (marker !== undefined && !initial) initial = [];
  if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
  effect(current => insertExpression(parent, accessor(), current, marker), initial);
}
typescript

目前我们只需要关心这两个参数:

  • parent:父元素
  • accessor: DOM 元素,由我们的模板生成的 DOM 元素

这里的 marker 是 undefined,所以不会初始化 initial,这里仍是 undefined。

因为这里的 accessor 不是函数,所以会直接调用 insertExpression 方法。

insertExpression(outer)

重新进入当前函数,这时 value 值是 div 元素,并且 div 元素内部已经存在文本内容 0。

function insertExpression(parent, value, current, marker, unwrapArray) {
  if (sharedConfig.context && !current) current = [...parent.childNodes];
  while (typeof current === "function") current = current();
  if (value === current) return current;
  const t = typeof value,
    multi = marker !== undefined;
  parent = (multi && current[0] && current[0].parentNode) || parent;

  if (t === "string" || t === "number") {
    // ...
  } else if (value == null || t === "boolean") {
 		// ...
  } else if (t === "function") {
 		// ...
  } else if (Array.isArray(value)) {
    // ...
  } else if (value instanceof Node) {
    if (sharedConfig.context && value.parentNode) return (current = multi ? [value] : value);
    if (Array.isArray(current)) {
      if (multi) return (current = cleanChildren(parent, current, marker, value));
      cleanChildren(parent, current, null, value);
    } else if (current == null || current === "" || !parent.firstChild) {
      parent.appendChild(value);
    } else parent.replaceChild(value, parent.firstChild);
    current = value;
  } else if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, value);

  return current;
}
typescript

这个方法会根据 value 的类型做不同处理,因为我们的 value 是一个 DOM 元素,所以会命中 value instance Node 分支。

因为此时的 parent.firstChild 并不存在,并且 current 是 undefined,所以会直接向 parent 追加元素。

最后将 vale 赋值给 current,并将其返回,insert 函数执行完毕。

回到 runUpdates 函数,此时已经执行完我们传入的 fn,页面已经渲染出模板结果。

function runUpdates<T>(fn: () => T, init: boolean) {
  if (Updates) return fn();
  let wait = false;
  if (!init) Updates = [];
  if (Effects) wait = true;
  else Effects = [];
  ExecCount++;
  try {
    const res = fn();
    completeUpdates(wait);
    return res;
  } catch (err) {
    if (!Updates) Effects = null;
    handleError(err);
  }
}
typescript

然后调用 completeUpdates 函数完成后续更新操作。

因为这里并不存在 Effects、Updates ,所以 completeUpdates 只是简单调用一下,并不会处理任何逻辑。

function completeUpdates(wait: boolean) {
  if (Updates) {
    if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
    else runQueue(Updates);
    Updates = null;
  }
  if (wait) return;
  let res;
  if (Transition && Transition.running) {
    // ...
  }
  const e = Effects!;
  Effects = null;
  if (e!.length) runUpdates(() => runEffects(e), false);
  else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
  if (res) res();
}
typescript

至此,首次渲染的代码我们就分析完了,接下来是更新过程。

更新流程

function HelloWorld() {
  const [count, setCount] = createSignal(0)

  setTimeout(() => {
    setCount(count() + 1)
  }, 1000);

  return html`<div>${ count }</div>`
}

render(HelloWorld, document.getElementById('app'))
typescript

当首次渲染完毕后,定时器到达时间会触发 setCount(count() + 1) 代码,从而触发更新流程。

首先会执行 count() ,调用 readSignal 方法,此时不存在 Listerner,所以只会返回 state 的值,即 0。

然后调用 setCount 方法,触发 writeSignal 方法。

writeSignal

export function writeSignal(node: SignalState<any> | Memo<any>, value: any, isComp?: boolean) {
  let current =
    Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;
  if (!node.comparator || !node.comparator(current, value)) {
    if (Transition) {
      const TransitionRunning = Transition.running;
      if (TransitionRunning || (!isComp && Transition.sources.has(node))) {
        Transition.sources.add(node);
        node.tValue = value;
      }
      if (!TransitionRunning) node.value = value;
    } else node.value = value;
    if (node.observers && node.observers.length) {
      runUpdates(() => {
        for (let i = 0; i < node.observers!.length; i += 1) {
          const o = node.observers![i];
          const TransitionRunning = Transition && Transition.running;
          if (TransitionRunning && Transition!.disposed.has(o)) continue;
          if ((TransitionRunning && !o.tState) || (!TransitionRunning && !o.state)) {
            if (o.pure) Updates!.push(o);
            else Effects!.push(o);
            if ((o as Memo<any>).observers) markDownstream(o as Memo<any>);
          }
          if (TransitionRunning) o.tState = STALE;
          else o.state = STALE;
        }
        if (Updates!.length > 10e5) {
          Updates = [];
          if ("_SOLID_DEV_") throw new Error("Potential Infinite Loop Detected.");
          throw new Error();
        }
      }, false);
    }
  }
  return value;
}
typescript

这段代码中 Transition 并不存在,所以会把传入的 value 值赋值给 node.value,即 signalState 对象,state 的值被更新为 1。

然后判断 node.observers 是否存在,这里的 observers 是存在的,是我们的 render computation 对象。

runUpdates

然后会调用 runUpdates 方法,内部匿名函数会将 computation 添加到 Effects 数组中。

function runUpdates<T>(fn: () => T, init: boolean) {
  if (Updates) return fn();
  let wait = false;
  if (!init) Updates = [];
  if (Effects) wait = true;
  else Effects = [];
  ExecCount++;
  try {
    const res = fn();
    completeUpdates(wait);
    return res;
  } catch (err) {
    if (!Updates) Effects = null;
    handleError(err);
  }
}
typescript

接着调用 completeUpdates 方法完成更新。

completeUpdates

function completeUpdates(wait: boolean) {
  if (Updates) {
    if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
    else runQueue(Updates);
    Updates = null;
  }
  if (wait) return;
  let res;
	// ...
  const e = Effects!;
  Effects = null;
  if (e!.length) runUpdates(() => runEffects(e), false);
  else if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate();
  if (res) res();
}
typescript

缓存 Effects,重置 Effects 为 null,然后调用 runUpdates 方法,传入 runEffects,顾名思义,就是执行副作用函数的意思。

值的注意的是,这里的 runEffects 不再是 runUserEffects,我们在使用 createEffect API 创建 computation 对象时会将 runEffects 重新赋值为 runUserEffects,在使用 createRenderEffect 时,并不是这样处理,这里的 runEffects 就是默认值 runQueue。

let runEffects = runQueue;
typescript

runEffects(runQueue)

function runQueue(queue: Computation<any>[]) {
  for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
typescript

然后就是遍历 computation 数组,调用 runTop 方法,参数为 computation 对象。

runTop

function runTop(node: Computation<any>) {
	// ....
  for (let i = ancestors.length - 1; i >= 0; i--) {
    node = ancestors[i];
    if (runningTransition) {
      let top = node,
        prev = ancestors[i + 1];
      while ((top = top.owner as Computation<any>) && top !== prev) {
        if (Transition!.disposed.has(top)) return;
      }
    }
    if (
      (!runningTransition && node.state === STALE) ||
      (runningTransition && node.tState === STALE)
    ) {
      updateComputation(node);
    } else if (
      (!runningTransition && node.state === PENDING) ||
      (runningTransition && node.tState === PENDING)
    ) {
      const updates = Updates;
      Updates = null;
      runUpdates(() => lookUpstream(node, ancestors[0]), false);
      Updates = updates;
    }
  }
}
typescript

调用 updateComputation 方法更新视图。

updateComputation

function updateComputation(node: Computation<any>) {
  if (!node.fn) return;
  cleanNode(node);
  const owner = Owner,
    listener = Listener,
    time = ExecCount;
  Listener = Owner = node;
  runComputation(
    node,
    Transition && Transition.running && Transition.sources.has(node as Memo<any>)
      ? (node as Memo<any>).tValue
      : node.value,
    time
  );

  if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
    queueMicrotask(() => {
      runUpdates(() => {
        Transition && (Transition.running = true);
        runComputation(node, (node as Memo<any>).tValue, time);
      }, false);
    });
  }
  Listener = listener;
  Owner = owner;
}
typescript

内部继续调用 runComputation 方法,将 computation 对象传入。

runComputation

function runComputation(node: Computation<any>, value: any, time: number) {
  let nextValue;
  try {
    nextValue = node.fn(value);
  } catch (err) {
    if (node.pure) Transition && Transition.running ? (node.tState = STALE) : (node.state = STALE);
    handleError(err);
  }
  if (!node.updatedAt || node.updatedAt <= time) {
    if (node.updatedAt != null && "observers" in (node as Memo<any>)) {
      writeSignal(node as Memo<any>, nextValue, true);
    } else if (Transition && Transition.running && node.pure) {
      Transition.sources.add(node as Memo<any>);
      (node as Memo<any>).tValue = nextValue;
    } else node.value = nextValue;
    node.updatedAt = time;
  }
}
typescript

然后执行 node.fn 方法。

export function insert(parent, accessor, marker, initial) {
  if (marker !== undefined && !initial) initial = [];
  if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
  effect(current => insertExpression(parent, accessor(), current, marker), initial);
}
typescript

这里的 node.fn 就是 current => insertExpression(parent, accessor(), current, marker)

后续流程其实我们已经很熟悉,就是获取到当前最新值,并且将值赋值给 div 元素,然后页面就会被更新,这就是 solid 的点对点更新。

总结

当我们使用 render 函数时返回模板时,solid 会对模板进行解析,生成可执行的代码片段。

如果存在响应式数据,会解析出响应式变量,调用 insert 方法,这时 solid 内部会自动创建一个 computation 对象,帮助我们把模板和副作用函数进行关联,只有这样我们在为响应式变量赋值时才可以达到更新视图的目的。

export function insert(parent, accessor, marker, initial) {
  if (marker !== undefined && !initial) initial = [];
  if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
  effect(current => insertExpression(parent, accessor(), current, marker), initial);
}
typescript

另外 solid 的模板解析是实现真实 DOM 细粒度更新的核心步骤,我们会在后面再点分析它是如何实现的。

调试案例