SolidJs nodiff原理浅析

基本原理

对jsx的理解

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

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

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

例子

scss 复制代码
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包一层,实现响应式。代码如下

sql 复制代码
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中

ini 复制代码
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. 值还是个函数,就循环计算出最终的值再设置

相关推荐
Moment22 分钟前
牛逼,NextJs 从 16.3 开始全面拥抱 Agent Native 🥰🥰🥰
前端·后端·面试
沸点小助手44 分钟前
6月沸点活动获奖名单公示|本周互动话题上新🎊
前端·后端
Csvn1 小时前
React 19 `use()` 来了:以后数据加载可以不用 useEffect?
前端·react.js
没落英雄1 小时前
从零开始搭建一个 AI Agent —— LangChain + TypeScript 实战手记
前端·人工智能·架构
远航_1 小时前
git submodule
前端·后端·github
摸着石头过河的石头1 小时前
从 Webpack 到 RSBuild:前端构建工具的进化之路
前端
疯狂的魔鬼1 小时前
告别 boolean 地狱:一个文件上传组件的状态机实践
前端·设计
竹林8181 小时前
Solana DApp 开发踩坑实录:从零用 @solana/web3.js 实现链上数据查询与交易签名
前端·javascript
狂师1 小时前
测试工程师的AI 技能库:推荐5个让你效率翻倍的Skills
前端·后端·测试
李明卫杭州1 小时前
Vue3 watch 与 watchEffect 深度解析
前端