SolidJs nodiff原理浅析

基本原理

对jsx的理解

jsx是一种声明语句,表示dom结构是怎样的。

虚拟dom的方式中,jsx最终转换成json对象,框架根据json对象patch对比,生成dom,而patch的过程会监听到响应式数据(vue),当数据变化,patch流程重新执行,实现响应式渲染。

solidjs编译时将jsx转换成创建dom的js代码,jsx静态部分直接创建dom,动态部分转换为插入删除等语句。而这些语句使用响应式系统的effect监听响应式数据,从而在数据变化时,重新执行,实现响应式。

例子

const App = () => {
  const [state, setState] = createSignal(0);

  const interval = setInterval(() =>
    setState(prv=>prv+1)
  , 1000);

  onCleanup(() => clearInterval(interval));

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

转换为
const root = document.getElementById('root');
const App = () => {
  const [state, setState] = createSignal(0);
  const interval = setInterval(() => setState(prv => prv + 1), 1000);
  onCleanup(() => clearInterval(interval));
  return (() => {
    const _el$ = _tmpl$();
    insert(_el$, state);
    return _el$;
  })();
};

上例中jsx转换成创建模板,绑定状态,返回dom三步。

insert函数包含了向dom设置状态的各种case的实现。如果状态是恒定的,直接调用实际的插入操作即可,如果是动态变化的,需要在外层用effect包一层,实现响应式。代码如下

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

实际的insert操作发生在insertExpression中

function insertExpression(parent, value, current, marker, unwrapArray) {
  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 (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") {
    current = cleanChildren(parent, current, marker);
  } else if (t === "function") {
    createRenderEffect(() => {
      let v = value();
      while (typeof v === "function") v = v();
      current = insertExpression(parent, v, current, marker);
    });
    return () => current;
  } else if (Array.isArray(value)) {
    const array = [];
    const currentArray = current && Array.isArray(current);
    if (normalizeIncomingArray(array, value, current, unwrapArray)) {
      createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));
      return () => current;
    }
    if (array.length === 0) {
      current = cleanChildren(parent, current, marker);
      if (multi) return current;
    } else if (currentArray) {
      if (current.length === 0) {
        appendNodes(parent, array, marker);
      } else reconcileArrays(parent, current, array);
    } else {
      current && cleanChildren(parent);
      appendNodes(parent, array);
    }
    current = array;
  } else if (value.nodeType) {
    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 console.warn(`Unrecognized value. Skipped inserting`, value);
  return current;
}

insertExpression实现的是将dom插入到父元素中。将根组件加到root中,使用这个函数,有两种case,只有一个节点和多个节点,对应代码里两个分支。将动态值设给dom,有三种case:1. 值是字符串/数字,设置textContent 2. 值是布尔或空,将元素清除 3. 值还是个函数,就循环计算出最终的值再设置

原文链接:https://juejin.cn/post/7343893453678231604 作者:Tomarsh

(0)
上一篇 2024年3月9日 下午4:15
下一篇 2024年3月9日 下午4:26

相关推荐

发表回复

登录后才能评论