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

相关推荐
夏幻灵11 分钟前
HTML5里最常用的十大标签
前端·html·html5
Mr Xu_24 分钟前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝28 分钟前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions36 分钟前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发36 分钟前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
程序员猫哥_44 分钟前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html
龙飞051 小时前
Systemd -systemctl - journalctl 速查表:服务管理 + 日志排障
linux·运维·前端·chrome·systemctl·journalctl
我爱加班、、1 小时前
Websocket能携带token过去后端吗
前端·后端·websocket
AAA阿giao1 小时前
从零拆解一个 React + TypeScript 的 TodoList:模块化、数据流与工程实践
前端·react.js·ui·typescript·前端框架
杨超越luckly1 小时前
HTML应用指南:利用GET请求获取中国500强企业名单,揭秘企业增长、分化与转型的新常态
前端·数据库·html·可视化·中国500强