💎 SolidJS 细粒度响应式

源码深度解析 - Fine-Grained Reactivity

基于 solid-js 源码深度分析
2026-03-20 | 技术深度解读

📑 目录

第一部分:核心概念

  • SolidJS 简介
  • 响应式原语
  • Signal 系统

第二部分:依赖追踪

  • Listener 机制
  • Computation 计算单元
  • 状态机设计

第三部分:调度系统

  • Scheduler 调度器
  • Transition 过渡
  • 批量更新

第四部分:组件系统

  • Owner 树结构
  • Context API
  • Resource 异步

💎 SolidJS 简介

SolidJS 是一个声明式、高效且灵活的 JavaScript UI 库

核心特点:

  • 细粒度响应式 - 无 Virtual DOM,直接更新 DOM
  • 编译时优化 - JSX 编译为真实 DOM 操作
  • 高性能 - 接近原生 JS 的执行速度
  • 类 React 语法 - 熟悉的组件和 Hooks 模式
// 典型 SolidJS 组件
function Counter() {
  const [count, setCount] = createSignal(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count()}
    </button>
  );
}

🏗️ 核心架构图

┌─────────────────────────────────────────────────────────────┐
│                    SolidJS 响应式系统                        │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │
│  │   Signal    │───▶│ Computation │───▶│    DOM      │     │
│  │  (状态源)   │    │  (计算单元) │    │  (渲染目标) │     │
│  └─────────────┘    └─────────────┘    └─────────────┘     │
│         │                  │                                 │
│         │    ┌─────────────▼─────────────┐                 │
│         │    │      Scheduler            │                 │
│         └───▶│  (调度器 - 批量更新)       │                 │
│              └───────────────────────────┘                 │
│                         │                                   │
│              ┌──────────▼──────────┐                       │
│              │    Transition       │                       │
│              │  (过渡 - 并发更新)   │                       │
│              └─────────────────────┘                       │
└─────────────────────────────────────────────────────────────┘

🔧 响应式原语概览

原语 用途 执行时机
createSignal 创建响应式状态 立即
createEffect 副作用(DOM 操作等) 渲染后
createMemo 缓存计算值 依赖变化时
createComputed 同步计算 渲染前
createRenderEffect 渲染阶段副作用 渲染时

📡 Signal 核心概念

Signal 是 SolidJS 响应式系统的基础单元

Signal 的组成:

  • Accessor - 读取函数
  • Setter - 设置函数
  • Value - 当前值
  • Observers - 观察者列表

核心特性:

  • 读取时自动追踪依赖
  • 写入时触发更新
  • 支持自定义比较器
  • 可选相等性检查
const [count, setCount] = createSignal(0);
// count() 读取值(追踪依赖)
// setCount(1) 设置值(触发更新)

📝 createSignal 源码

export function createSignal<T>(
  value?: T,
  options?: SignalOptions<T | undefined>
): Signal<T | undefined> {
  options = options ? Object.assign({}, signalOptions, options) : signalOptions;

  // 创建 Signal 状态对象
  const s: SignalState<T | undefined> = {
    value,                    // 当前值
    observers: null,          // 观察者列表
    observerSlots: null,      // 观察者的槽位索引
    comparator: options.equals || undefined  // 比较函数
  };

  // 开发模式注册到图
  if (IS_DEV) {
    if (options.name) s.name = options.name;
    if (!options.internal) registerGraph(s);
  }

  // 创建 setter 函数
  const setter: Setter<T | undefined> = (value?: unknown) => {
    if (typeof value === "function") {
      // 支持函数式更新
      value = Transition?.running ? value(s.tValue) : value(s.value);
    }
    return writeSignal(s, value);
  };

  return [readSignal.bind(s), setter];
}

📊 SignalState 接口

export interface SignalState<T> extends SourceMapValue {
  value: T;                         // 当前值
  observers: Computation<any>[] | null;   // 观察者数组
  observerSlots: number[] | null;   // 观察者在 sources 中的索引
  tValue?: T;                       // Transition 期间的临时值
  comparator?: (prev: T, next: T) => boolean;  // 自定义比较器
  internal?: true;                  // 内部信号标记
}

双向依赖关系:

  • Signal → Observers:知道谁在监听自己
  • Computation → Sources:知道自己依赖谁
  • observerSlots:快速定位依赖位置,O(1) 删除

📖 readSignal 实现

export function readSignal(this: SignalState<any> | Memo<any>) {
  const runningTransition = Transition && Transition.running;
  
  // 如果是 Memo 且状态过期,先更新自己
  if ((this as Memo<any>).sources && 
      (runningTransition ? (this as Memo<any>).tState : (this as Memo<any>).state)) {
    if ((runningTransition ? (this as Memo<any>).tState : (this as Memo<any>).state) === STALE) {
      updateComputation(this as Memo<any>);
    } else {
      const updates = Updates;
      Updates = null;
      runUpdates(() => lookUpstream(this as Memo<any>), false);
      Updates = updates;
    }
  }

  // 核心:建立依赖关系
  if (Listener) {
    const sSlot = this.observers ? this.observers.length : 0;
    
    // Listener 记录依赖
    if (!Listener.sources) {
      Listener.sources = [this];
      Listener.sourceSlots = [sSlot];
    } else {
      Listener.sources.push(this);
      Listener.sourceSlots!.push(sSlot);
    }
    
    // Signal 记录观察者
    if (!this.observers) {
      this.observers = [Listener];
      this.observerSlots = [Listener.sources.length - 1];
    } else {
      this.observers.push(Listener);
      this.observerSlots!.push(Listener.sources.length - 1);
    }
  }

  return runningTransition && Transition!.sources.has(this) 
    ? this.tValue 
    : this.value;
}

✏️ writeSignal 实现

export function writeSignal(
  node: SignalState<any> | Memo<any>, 
  value: any, 
  isComp?: boolean
) {
  let current = Transition?.running && Transition.sources.has(node) 
    ? node.tValue 
    : node.value;

  // 相等性检查
  if (!node.comparator || !node.comparator(current, value)) {
    // Transition 模式:写入 tValue
    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 : !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.state = STALE;
          else o.tState = STALE;
        }
        // 防止无限循环
        if (Updates!.length > 10e5) {
          Updates = [];
          throw new Error("Potential Infinite Loop Detected.");
        }
      }, false);
    }
  }
  return value;
}

🔗 依赖追踪原理

SolidJS 使用运行时自动依赖收集,无需显式声明

┌────────────────────────────────────────────────────────┐
│                  依赖追踪流程                           │
├────────────────────────────────────────────────────────┤
│                                                        │
│  Effect 执行时                                         │
│       │                                                │
│       ▼                                                │
│  ┌─────────┐    设置 Listener = 当前 Computation      │
│  │ 读取    │─────────────────────────────────▶        │
│  │ Signal  │                                          │
│  └─────────┘                                          │
│       │                                                │
│       ▼                                                │
│  ┌─────────────────────────────────────┐              │
│  │ readSignal() 检测到 Listener 存在    │              │
│  │                                     │              │
│  │  1. Signal.observers.push(Listener) │              │
│  │  2. Listener.sources.push(Signal)   │              │
│  │  3. 记录双向索引                     │              │
│  └─────────────────────────────────────┘              │
│                                                        │
└────────────────────────────────────────────────────────┘

👂 Listener 机制

Listener 是当前正在执行的 Computation 引用

let Listener: Computation<any> | null = null;

function runComputation(node: Computation<any>, value: any, time: number) {
  let nextValue;
  const owner = Owner,
    listener = Listener;
  
  // 设置当前 Listener
  Listener = Owner = node;
  
  try {
    nextValue = node.fn(value);
  } catch (err) {
    // 错误处理
    return handleError(err);
  } finally {
    // 恢复之前的 Listener
    Listener = listener;
    Owner = owner;
  }
  // ...
}

Listener 的作用:

  • 标记当前执行上下文
  • readSignal 时自动建立依赖
  • 支持嵌套 Computation

⚙️ Computation 接口

export interface Computation<Init, Next extends Init = Init> extends Owner {
  fn: EffectFunction<Init, Next>;      // 计算函数
  state: ComputationState;              // 当前状态
  tState?: ComputationState;            // Transition 状态
  sources: SignalState<Next>[] | null; // 依赖的 Signal 列表
  sourceSlots: number[] | null;         // 在 Signal.observers 中的索引
  value?: Init;                         // 当前计算值
  updatedAt: number | null;             // 最后更新时间戳
  pure: boolean;                        // 是否纯计算(Memo)
  user?: boolean;                       // 是否用户 Effect
  suspense?: SuspenseContextType;       // Suspense 上下文
}

export type ComputationState = 0 | 1 | 2;
// 0 = CLEAN(干净,无需更新)
// 1 = STALE(过期,需要重新计算)
// 2 = PENDING(待定,依赖可能变化)

💥 createEffect 源码

export function createEffect<Next, Init>(
  fn: EffectFunction<Init | Next, Next>,
  value?: Init,
  options?: EffectOptions & { render?: boolean }
): void {
  runEffects = runUserEffects;
  
  // 创建 Computation
  const c = createComputation(fn, value!, false, STALE, IS_DEV ? options : undefined),
    s = SuspenseContext && useContext(SuspenseContext);
  
  if (s) c.suspense = s;
  if (!options || !options.render) c.user = true;
  
  // 添加到 Effects 队列或立即执行
  Effects ? Effects.push(c) : updateComputation(c);
}

createEffect 特点:

  • 异步执行 - 渲染后才运行
  • 自动追踪依赖
  • 支持 Suspense
  • 标记为 user effect

🧮 createMemo 源码

export function createMemo<Next extends Prev, Init, Prev>(
  fn: EffectFunction<Init | Prev, Next>,
  value?: Init,
  options?: MemoOptions<Next>
): Accessor<Next> {
  options = options ? Object.assign({}, signalOptions, options) : signalOptions;

  // 创建 Memo(既是 Signal 又是 Computation)
  const c: Partial<Memo<Init, Next>> = createComputation(
    fn,
    value!,
    true,           // pure = true
    0,              // 初始状态 CLEAN
    IS_DEV ? options : undefined
  ) as Partial<Memo<Init, Next>>;

  c.observers = null;
  c.observerSlots = null;
  c.comparator = options.equals || undefined;
  
  // 立即执行或加入队列
  if (Scheduler && Transition && Transition.running) {
    c.tState = STALE;
    Updates!.push(c as Memo<Init, Next>);
  } else {
    updateComputation(c as Memo<Init, Next>);
  }
  
  return readSignal.bind(c as Memo<Init, Next>);
}

⚡ createComputed

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

createComputed

  • 同步执行
  • 渲染前运行
  • 用于派生状态

createEffect

  • 异步执行
  • 渲染后运行
  • 用于副作用

🔄 updateComputation

function updateComputation(node: Computation<any>) {
  if (!node.fn) return;
  
  // 清理旧依赖
  cleanNode(node);
  
  const time = ExecCount;
  
  // 执行计算
  runComputation(
    node,
    Transition && Transition.running && Transition.sources.has(node as Memo<any>)
      ? (node as Memo<any>).tValue
      : node.value,
    time
  );

  // Transition 完成后同步
  if (Transition && !Transition.running && Transition.sources.has(node as Memo<any>)) {
    queueMicrotask(() => {
      runUpdates(() => {
        Transition && (Transition.running = true);
        Listener = Owner = node;
        runComputation(node, (node as Memo<any>).tValue, time);
        Listener = Owner = null;
      }, false);
    });
  }
}

🎯 状态机设计

Computation 状态转换图:

                    依赖变化
    ┌─────────┐ ───────────────▶ ┌─────────┐
    │  CLEAN  │                  │  STALE  │
    │  (0)    │ ◀─────────────── │  (1)    │
    └─────────┘   重新计算完成    └────┬────┘
         ▲                              │
         │                              │
         │          ┌───────────────────┘
         │          │ 依赖也是 STALE
         │          ▼
         │    ┌──────────┐
         └────│ PENDING  │
              │   (2)    │
              └──────────┘

状态含义:
- CLEAN (0):无需更新
- STALE (1):需要重新计算
- PENDING (2):依赖可能变化,需要向上查找

📦 批量更新机制

SolidJS 通过 runUpdates 实现批量更新

let Updates: Computation<any>[] | null = null;
let Effects: Computation<any>[] | null = null;

function runUpdates<T>(fn: () => T, init: boolean): T | undefined {
  if (Updates) return fn();  // 已在更新周期中
  
  Updates = [];
  Effects = [];
  ExecCount++;
  
  try {
    const ret = fn();
    
    // 处理 Updates 队列
    finishPending(Updates);
    Updates = null;
    
    // 处理 Effects 队列
    if (Effects.length) {
      runEffects(Effects);
      Effects = null;
    }
    
    return ret;
  } finally {
    // 清理
  }
}

🔄 runUpdates 实现

function finishPending(queue: Computation<any>[]) {
  for (let i = 0; i < queue.length; i++) {
    const node = queue[i];
    
    // 如果状态不是 STALE,说明是 PENDING,需要向上查找
    if ((Transition && Transition.running ? node.tState : node.state) === PENDING) {
      lookUpstream(node);
    }
    
    // 如果状态变成 STALE,执行更新
    if ((Transition && Transition.running ? node.tState : node.state) === STALE) {
      runTop(node);
    }
  }
}

批量更新优势:

  • 多个 Signal 变更只触发一次渲染
  • 自动去重,避免重复计算
  • 依赖拓扑排序保证顺序

⏰ Scheduler 调度器

SolidJS 的调度器参考了 React 的 Scheduler 实现

export interface Task {
  id: number;
  fn: ((didTimeout: boolean) => void) | null;
  startTime: number;
  expirationTime: number;
}

let taskQueue: Task[] = [];
let isCallbackScheduled = false;
let isPerformingWork = false;
let currentTask: Task | null = null;

调度器目标:

  • 优先级调度 - 高优先级任务先执行
  • 时间切片 - 避免长时间阻塞主线程
  • 协作式调度 - 可中断可恢复

📊 Task 优先级队列

// 使用二分查找插入,按 expirationTime 排序
function enqueue(taskQueue: Task[], task: Task) {
  function findIndex() {
    let m = 0;
    let n = taskQueue.length - 1;

    while (m <= n) {
      const k = (n + m) >> 1;
      const cmp = task.expirationTime - taskQueue[k].expirationTime;
      if (cmp > 0) m = k + 1;
      else if (cmp < 0) n = k - 1;
      else return k;
    }
    return m;
  }
  taskQueue.splice(findIndex(), 0, task);
}

export function requestCallback(fn: () => void, options?: { timeout: number }): Task {
  if (!scheduleCallback) setupScheduler();
  
  let startTime = performance.now(),
    timeout = options?.timeout ?? maxSigned31BitInt;

  const newTask: Task = {
    id: taskIdCounter++,
    fn,
    startTime,
    expirationTime: startTime + timeout
  };

  enqueue(taskQueue, newTask);
  // ...调度执行
}

📡 MessageChannel 调度

function setupScheduler() {
  const channel = new MessageChannel(),
    port = channel.port2;
  
  scheduleCallback = () => port.postMessage(null);
  
  channel.port1.onmessage = () => {
    if (scheduledCallback !== null) {
      const currentTime = performance.now();
      deadline = currentTime + yieldInterval;      // 5ms
      maxDeadline = currentTime + maxYieldInterval; // 300ms
      
      try {
        const hasMoreWork = scheduledCallback(currentTime);
        if (!hasMoreWork) {
          scheduledCallback = null;
        } else {
          port.postMessage(null);  // 继续下一帧
        }
      } catch (error) {
        port.postMessage(null);
        throw error;
      }
    }
  };
}

为什么用 MessageChannel?

  • 比 setTimeout 更快(宏任务中最快)
  • 不阻塞用户交互
  • 适合高频更新场景

👆 isInputPending 优化

if (navigator?.scheduling?.isInputPending) {
  const scheduling = navigator.scheduling;
  shouldYieldToHost = () => {
    const currentTime = performance.now();
    if (currentTime >= deadline) {
      // 有用户输入待处理,必须让出
      if (scheduling.isInputPending()) {
        return true;
      }
      // 没有输入,可以继续到最大时间
      return currentTime >= maxDeadline;
    }
    return false;
  };
} else {
  // 回退方案:固定时间切片
  shouldYieldToHost = () => performance.now() >= deadline;
}

isInputPending API:

  • 浏览器实验性 API
  • 检测是否有待处理的用户输入
  • 优化响应式体验

🔁 workLoop 实现

function workLoop(initialTime: number) {
  let currentTime = initialTime;
  currentTask = taskQueue[0] || null;
  
  while (currentTask !== null) {
    // 检查是否需要让出主线程
    if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
      break;  // 时间片用完,让出
    }
    
    const callback = currentTask.fn;
    if (callback !== null) {
      currentTask.fn = null;
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      callback(didUserCallbackTimeout);
      currentTime = performance.now();
      
      if (currentTask === taskQueue[0]) {
        taskQueue.shift();
      }
    } else {
      taskQueue.shift();
    }
    
    currentTask = taskQueue[0] || null;
  }
  
  // 返回是否还有工作
  return currentTask !== null;
}

🌊 Transition 机制

Transition 是 SolidJS 的并发更新机制

export interface TransitionState {
  sources: Set<SignalState<any>>;      // 参与的 Signal
  effects: Computation<any>[];          // 待执行 Effect
  promises: Set<Promise<any>>;          // 异步 Promise
  disposed: Set<Computation<any>>;      // 已销毁的 Computation
  queue: Set<Computation<any>>;         // 待处理队列
  scheduler?: (fn: () => void) => unknown;
  running: boolean;                     // 是否正在运行
  done?: Promise<void>;                 // 完成信号
  resolve?: () => void;                 // resolve 函数
}

🚀 startTransition

export function startTransition(fn: () => unknown): Promise<void> {
  if (Transition && Transition.running) {
    fn();
    return Transition.done!;
  }
  
  const l = Listener;
  const o = Owner;
  
  return Promise.resolve().then(() => {
    Listener = l;
    Owner = o;
    
    let t: TransitionState | undefined;
    if (Scheduler || SuspenseContext) {
      t = Transition || (Transition = {
        sources: new Set(),
        effects: [],
        promises: new Set(),
        disposed: new Set(),
        queue: new Set(),
        running: true
      });
      t.done ||= new Promise(res => (t!.resolve = res));
      t.running = true;
    }
    
    runUpdates(fn, false);
    Listener = Owner = null;
    return t ? t.done : undefined;
  });
}

🎣 useTransition

const [transPending, setTransPending] = /*@__PURE__*/ createSignal(false);

export function useTransition(): Transition {
  return [transPending, startTransition];
}

// 使用示例
function App() {
  const [isPending, start] = useTransition();
  const [tab, setTab] = createSignal('about');
  
  const updateTab = () => {
    start(() => {
      setTab('posts');  // 低优先级更新
    });
  };
  
  return (
    <div>
      {isPending() && <Spinner />}
      <button onClick={updateTab}>Switch Tab</button>
    </div>
  );
}

🧩 组件系统

SolidJS 组件是纯函数,只在初始化时执行一次

// 组件类型定义
export type Component<P extends Record<string, any> = {}> = 
  (props: P) => JSX.Element;

export type ParentComponent<P = {}> = 
  Component<P & { children?: JSX.Element }>;

export type FlowComponent<P = {}, C = JSX.Element> = 
  Component<P & { children: C }>;

组件特点:

  • 只执行一次(不像 React 每次渲染)
  • props 是响应式的
  • 返回值是 DOM 节点或组件

🏗️ createComponent

export function createComponent<T extends Record<string, any>>(
  Comp: Component<T>,
  props: T
): JSX.Element {
  if (hydrationEnabled) {
    if (sharedConfig.context) {
      const c = sharedConfig.context;
      setHydrateContext(nextHydrateContext());
      const r = IS_DEV
        ? devComponent(Comp, props || ({} as T))
        : untrack(() => Comp(props || ({} as T)));
      setHydrateContext(c);
      return r;
    }
  }
  
  // 生产环境:untrack 下执行组件
  if (IS_DEV) return devComponent(Comp, props || ({} as T));
  return untrack(() => Comp(props || ({} as T)));
}

🔗 mergeProps 实现

export function mergeProps<T extends unknown[]>(...sources: T): MergeProps<T> {
  let proxy = false;
  
  // 检查是否有 proxy 或函数
  for (let i = 0; i < sources.length; i++) {
    const s = sources[i];
    proxy = proxy || (!!s && $PROXY in (s as object));
    sources[i] = typeof s === "function" 
      ? ((proxy = true), createMemo(s as EffectFunction<unknown>)) 
      : s;
  }
  
  // Proxy 实现(支持响应式 props)
  if (SUPPORTS_PROXY && proxy) {
    return new Proxy({
      get(property) {
        for (let i = sources.length - 1; i >= 0; i--) {
          const v = resolveSource(sources[i])[property];
          if (v !== undefined) return v;
        }
      },
      has(property) { /* ... */ },
      keys() { /* ... */ }
    }, propTraps) as unknown as MergeProps<T>;
  }
  
  // 非 Proxy 回退
  // ...
}

✂️ splitProps 实现

export function splitProps<
  T extends Record<any, any>,
  K extends [readonly (keyof T)[], ...(readonly (keyof T)[])[]]
>(props: T, ...keys: K): SplitProps<T, K> {
  const len = keys.length;

  if (SUPPORTS_PROXY && $PROXY in props) {
    const blocked = len > 1 ? keys.flat() : keys[0];
    
    // 为每个 key 组创建 Proxy
    const res = keys.map(k => {
      return new Proxy({
        get(property) {
          return k.includes(property) ? props[property as any] : undefined;
        },
        has(property) {
          return k.includes(property) && property in props;
        },
        keys() {
          return k.filter(property => property in props);
        }
      }, propTraps);
    });
    
    // 剩余 props
    res.push(new Proxy({
      get(property) {
        return blocked.includes(property) ? undefined : props[property as any];
      },
      // ...
    }, propTraps));
    
    return res as SplitProps<T, K>;
  }
  // ...
}

🌱 createRoot

export function createRoot<T>(fn: RootFunction<T>, detachedOwner?: typeof Owner): T {
  const listener = Listener,
    owner = Owner,
    unowned = fn.length === 0,
    current = detachedOwner === undefined ? owner : detachedOwner;
  
  // 创建新的 Owner
  const root: Owner = unowned
    ? IS_DEV
      ? { owned: null, cleanups: null, context: null, owner: null }
      : UNOWNED
    : {
        owned: null,
        cleanups: null,
        context: current ? current.context : null,
        owner: current
      };

  Owner = root;
  Listener = null;

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

🌳 Owner 树结构

Owner 树管理组件生命周期:

        createRoot
            │
            ▼
        App (Owner)
         │
         ├── Component A (owned)
         │    │
         │    ├── createEffect
         │    │
         │    └── createMemo
         │
         └── Component B (owned)
              │
              ├── createEffect
              │
              └── Component C (owned)
                   │
                   └── createEffect

清理规则:
- 父组件销毁时,所有子组件和 Effect 自动清理
- onCleanup 按照创建的逆序执行
- context 沿着 Owner 树向上查找

🧹 onCleanup 清理

export function onCleanup<T extends () => any>(fn: T): T {
  if (Owner === null) {
    IS_DEV && console.warn(
      "cleanups created outside a `createRoot` or `render` will never be run"
    );
  } else if (Owner.cleanups === null) {
    Owner.cleanups = [fn];
  } else {
    Owner.cleanups.push(fn);
  }
  return fn;
}

function cleanNode(node: Owner) {
  // 执行清理函数
  if (node.cleanups) {
    for (let i = 0; i < node.cleanups.length; i++) {
      node.cleanups[i]();
    }
    node.cleanups = null;
  }
  
  // 清理所有子节点
  if (node.owned) {
    for (let i = 0; i < node.owned.length; i++) {
      cleanNode(node.owned[i]);
    }
    node.owned = null;
  }
}

📦 Context API

export interface Context<T> {
  id: symbol;
  Provider: ContextProviderComponent<T>;
  defaultValue: T;
}

export function createContext<T>(
  defaultValue?: T,
  options?: EffectOptions
): Context<T | undefined> {
  const id = Symbol("context");
  return { id, Provider: createProvider(id, options), defaultValue };
}

export function useContext<T>(context: Context<T>): T {
  let value: undefined | T;
  return Owner && Owner.context && (value = Owner.context[context.id]) !== undefined
    ? value
    : context.defaultValue;
}

Context 特点:

  • 使用 Symbol 作为唯一标识
  • 沿着 Owner 树向上查找
  • Provider 是组件形式

🌐 createResource

export function createResource<T, S, R>(
  source: ResourceSource<S>,
  fetcher: ResourceFetcher<S, T, R>,
  options?: ResourceOptions<T, S>
): ResourceReturn<T, R> {
  // ...
  
  const [value, setValue] = createSignal<T | undefined>(options.initialValue),
    [error, setError] = createSignal<unknown>(undefined),
    [state, setState] = createSignal<"unresolved" | "pending" | "ready" | "refreshing">(
      resolved ? "ready" : "unresolved"
    );

  function read() {
    const c = SuspenseContext && useContext(SuspenseContext);
    if (err !== undefined && !pr) throw err;
    
    // Suspense 集成
    if (Listener && !Listener.user && c) {
      createComputed(() => {
        track();
        if (pr) {
          c.increment!();
          contexts.add(c);
        }
      });
    }
    return value();
  }
  
  // ...
}

⏳ Suspense 集成

export type SuspenseContextType = {
  increment?: () => void;    // 增加挂起计数
  decrement?: () => void;    // 减少挂起计数
  inFallback?: () => boolean;
  effects?: Computation<any>[];
  resolved?: boolean;
};

let SuspenseContext: SuspenseContext;

export function getSuspenseContext() {
  return SuspenseContext || (SuspenseContext = createContext<SuspenseContextType | undefined>());
}

Suspense 工作原理:

  • Resource 读取时检查是否有 pending promise
  • 有的话 throw promise 给 Suspense 边界
  • Suspense 显示 fallback 直到 resolve

💤 lazy 懒加载

export function lazy<T extends Component<any>>(
  fn: () => Promise<{ default: T }>
): T & { preload: () => Promise<{ default: T }> } {
  let comp: () => T | undefined;
  let p: Promise<{ default: T }> | undefined;
  
  const wrap: T & { preload?: () => void } = ((props: any) => {
    const ctx = sharedConfig.context;
    
    if (ctx) {
      // SSR hydration
      const [s, set] = createSignal<T>();
      (p || (p = fn())).then(mod => {
        set(() => mod.default);
      });
      comp = s;
    } else if (!comp) {
      // 客户端懒加载
      const [s] = createResource<T>(() => 
        (p || (p = fn())).then(mod => mod.default)
      );
      comp = s;
    }
    
    return createMemo(() => comp() ? untrack(() => comp()(props)) : "");
  }) as T;
  
  wrap.preload = () => p || ((p = fn()).then(mod => (comp = () => mod.default)), p);
  
  return wrap as T & { preload: () => Promise<{ default: T }> };
}

🎭 Dynamic 组件

export function createDynamic<T extends ValidComponent>(
  component: () => T | undefined,
  props: ComponentProps<T>
): JSX.Element {
  const cached = createMemo<Function | string | undefined>(component);
  
  return createMemo(() => {
    const component = cached();
    
    switch (typeof component) {
      case "function":
        if (isDev) Object.assign(component, { [$DEVCOMP]: true });
        return untrack(() => component(props));
        
      case "string":
        const isSvg = SVGElements.has(component);
        const el = sharedConfig.context
          ? getNextElement()
          : createElement(component, isSvg, untrack(() => props.is));
        spread(el, props, isSvg);
        return el;
    }
  }) as unknown as JSX.Element;
}

// 使用
<Dynamic component={multiline() ? 'textarea' : 'input'} value={value()} />

🚪 Portal 实现

export function Portal(props: {
  mount?: Node;
  useShadow?: boolean;
  isSVG?: boolean;
  children: JSX.Element;
}) {
  const marker = document.createTextNode(""),
    mount = () => props.mount || document.body,
    owner = getOwner();
  
  let content: undefined | (() => JSX.Element);
  
  createEffect(() => {
    content || (content = runWithOwner(owner, () => createMemo(() => props.children)));
    const el = mount();
    
    if (el instanceof HTMLHeadElement) {
      // Head 元素特殊处理
      createRoot(dispose => insert(el, () => (!clean() ? content!() : dispose()), null));
    } else {
      // 创建容器
      const container = createElement(props.isSVG ? "g" : "div", props.isSVG),
        renderRoot = props.useShadow 
          ? container.attachShadow({ mode: "open" }) 
          : container;
      
      insert(renderRoot, content);
      el.appendChild(container);
      onCleanup(() => el.removeChild(container));
    }
  });
  
  return marker;
}

🎯 createSelector

用于高效列表选中状态的优化原语

export function createSelector<T, U = T>(
  source: Accessor<T>,
  fn: EqualityCheckerFunction<T, U> = equalFn as TODO
): (key: U) => boolean {
  const subs = new Map<U, Set<Computation<any>>>();
  
  const node = createComputation(
    (p: T | undefined) => {
      const v = source();
      // 只通知状态变化的观察者
      for (const [key, val] of subs.entries()) {
        if (fn(key, v) !== fn(key, p!)) {
          for (const c of val.values()) {
            c.state = STALE;
            if (c.pure) Updates!.push(c);
            else Effects!.push(c);
          }
        }
      }
      return v;
    },
    undefined,
    true,
    STALE
  ) as Memo<any>;
  
  updateComputation(node);
  
  return (key: U) => {
    // 动态订阅
    if (Listener) {
      let l = subs.get(key) || subs.set(key, new Set([Listener])).get(key)!;
      l.add(listener);
      onCleanup(() => { l.delete(listener); !l.size && subs.delete(key); });
    }
    return fn(key, node.value!);
  };
}

🔓 untrack 工具

untrack 在执行时暂停依赖追踪

export function untrack<T>(fn: Accessor<T>): T {
  if (!ExternalSourceConfig && Listener === null) return fn();

  const listener = Listener;
  Listener = null;
  
  try {
    if (ExternalSourceConfig) return ExternalSourceConfig.untrack(fn);
    return fn();
  } finally {
    Listener = listener;
  }
}

// 使用示例
createEffect(() => {
  const value = signalA();      // 被追踪
  const other = untrack(() => signalB());  // 不被追踪
  // effect 只依赖 signalA
});

📦 batch 批处理

export function batch<T>(fn: Accessor<T>): T {
  return runUpdates(fn, false) as T;
}

// 使用示例
function updateAll() {
  batch(() => {
    setFirstName("John");
    setLastName("Doe");
    setAge(30);
    // 所有更新合并为一次渲染
  });
}

batch 的效果:

  • 多个 Signal 更新合并
  • 只触发一次 Effect 执行
  • 内部自动使用,外部可选

📋 on 依赖声明

export function on<S, Next>(
  deps: AccessorArray<S> | Accessor<S>,
  fn: OnEffectFunction<S, undefined | NoInfer<Prev>, Next>,
  options?: OnOptions
): EffectFunction<undefined | NoInfer<Next>> {
  const isArray = Array.isArray(deps);
  let prevInput: S;
  let defer = options && options.defer;
  
  return prevValue => {
    let input: S;
    if (isArray) {
      input = Array(deps.length) as unknown as S;
      for (let i = 0; i < deps.length; i++) 
        (input as unknown as any[])[i] = deps[i]();
    } else {
      input = deps();
    }
    
    if (defer) {
      defer = false;
      return prevValue;
    }
    
    const result = untrack(() => fn(input, prevInput, prevValue));
    prevInput = input;
    return result;
  };
}

// 使用
createEffect(on([a, b], ([v1, v2]) => {
  // 只在 a 或 b 变化时执行
}));

⚡ 性能对比

框架 更新机制 首次渲染 更新速度
SolidJS 细粒度响应式 极快 极快
React Virtual DOM 中等
Vue 3 响应式 + VDOM
Svelte 编译时 极快 极快

SolidJS 性能优势:

  • 无 Virtual DOM diff 开销
  • 精确到 DOM 节点级别的更新
  • 编译时优化,运行时轻量

🆚 vs React Hooks

React Hooks

  • 每次渲染重新执行
  • 依赖数组手动声明
  • 闭包陷阱问题
  • useEffect 时机复杂

SolidJS Primitives

  • 只执行一次
  • 自动依赖追踪
  • 无闭包问题
  • 清晰的执行时机
// React - 每次渲染都执行
useEffect(() => {
  console.log(count);  // 闭包问题
}, [count]);

// SolidJS - 只执行一次,自动追踪
createEffect(() => {
  console.log(count());  // 总是最新值
});

🆚 vs Vue Reactivity

Vue 3 Reactivity

  • Proxy-based
  • 仍使用 Virtual DOM
  • .value 访问(Ref)
  • 模板编译

SolidJS Reactivity

  • Signal-based
  • 直接 DOM 操作
  • 函数调用访问
  • JSX 编译
// Vue 3
const count = ref(0);
console.log(count.value);

// SolidJS
const [count, setCount] = createSignal(0);
console.log(count());

✅ 最佳实践

DO

  • Signal 用函数访问
  • 组件内用 createEffect
  • 派生状态用 createMemo
  • 用 untrack 避免不必要依赖
  • 复杂逻辑用 batch

DON'T

  • 解构 Signal 值
  • 在 Signal 外缓存值
  • 过度使用 createMemo
  • 忽略清理副作用
  • 在循环中创建 Effect
// ❌ 错误
const value = signal();  // 失去响应性

// ✅ 正确
const value = signal();  // 在需要时调用

⚠️ 常见陷阱

// 陷阱 1:解构失去响应性
const { name } = props;  // ❌
// 正确:props.name

// 陷阱 2:条件中使用 Signal
if (signal()) { }  // 只在初始化时读取
// 正确:createMemo(() => signal() && ...)

// 陷阱 3:事件处理器中的闭包
onClick={() => console.log(count())}  // ✅ 正确,每次都读取最新值

// 陷阱 4:忘记清理
createEffect(() => {
  const timer = setInterval(...);
  // 忘记 onCleanup
});
// 正确:
createEffect(() => {
  const timer = setInterval(...);
  onCleanup(() => clearInterval(timer));
});

🎯 总结

SolidJS 响应式系统核心:

  • Signal - 响应式状态的基本单元
  • Computation - 自动追踪依赖的计算单元
  • Scheduler - 高效的批量更新调度
  • Owner - 组件树生命周期管理

设计哲学:

  • 细粒度响应式 - 精确更新
  • 编译时优化 - 运行时轻量
  • 类 React API - 低学习成本
  • 高性能 - 接近原生 JS

💎 SolidJS - 简单、高效、强大