响应式基础 #

一、响应式系统概述 #

1.1 什么是响应式 #

响应式系统是一种自动追踪数据依赖并在数据变化时自动更新的机制。

text
┌─────────────────────────────────────────────────────────────┐
│                    响应式系统工作原理                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. 依赖收集阶段                                           │
│   ┌─────────┐     读取      ┌─────────┐                    │
│   │ Signal  │ ───────────→ │ Effect  │                    │
│   │ (状态)  │              │ (副作用) │                    │
│   └─────────┘              └────┬────┘                    │
│       ↑                         │                          │
│       │      记录依赖           │                          │
│       └─────────────────────────┘                          │
│                                                             │
│   2. 触发更新阶段                                           │
│   ┌─────────┐     通知      ┌─────────┐                    │
│   │ Signal  │ ───────────→ │ Effect  │                    │
│   │ (变化)  │              │ (执行)  │                    │
│   └─────────┘              └─────────┘                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 Solid 响应式特点 #

特点 说明
细粒度 精确追踪依赖,最小化更新
自动 无需手动声明依赖
同步 状态变化立即触发更新
无虚拟DOM 直接更新 DOM 节点
高效 只执行必要的计算

二、响应式原语 #

2.1 三大核心原语 #

jsx
import { createSignal, createMemo, createEffect } from 'solid-js';

// Signal - 状态源
const [count, setCount] = createSignal(0);

// Memo - 派生状态
const doubled = createMemo(() => count() * 2);

// Effect - 副作用
createEffect(() => {
  console.log(`Count: ${count()}, Doubled: ${doubled()}`);
});

2.2 原语关系图 #

text
┌─────────────────────────────────────────────────────────────┐
│                    响应式原语关系                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   createSignal (状态源)                                     │
│        │                                                    │
│        ├──→ createMemo (派生状态)                           │
│        │         │                                          │
│        │         └──→ createEffect (副作用)                 │
│        │                                                    │
│        └──→ createEffect (副作用)                           │
│                                                             │
│   特点:                                                    │
│   - Signal 是可写的                                         │
│   - Memo 是只读的派生值                                     │
│   - Effect 用于执行副作用                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

三、依赖追踪 #

3.1 自动依赖追踪 #

Solid 自动追踪 Signal 的依赖关系:

jsx
function Counter() {
  const [count, setCount] = createSignal(0);
  const [name, setName] = createSignal('Alice');

  // 这个 Effect 只依赖 count
  createEffect(() => {
    console.log('Count changed:', count());
  });

  // 这个 Effect 只依赖 name
  createEffect(() => {
    console.log('Name changed:', name());
  });

  // 这个 Effect 依赖 count 和 name
  createEffect(() => {
    console.log('Both:', count(), name());
  });

  return (
    <div>
      <p>Count: {count()}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

3.2 依赖追踪原理 #

text
执行 Effect 时:

1. 进入追踪模式
   ┌─────────────────────────────────────┐
   │ activeEffect = currentEffect        │
   │ tracking = true                     │
   └─────────────────────────────────────┘

2. 执行函数体
   ┌─────────────────────────────────────┐
   │ count() 被调用                       │
   │     ↓                               │
   │ Signal 检测到正在追踪                │
   │     ↓                               │
   │ 将当前 Effect 添加到依赖列表         │
   └─────────────────────────────────────┘

3. 退出追踪模式
   ┌─────────────────────────────────────┐
   │ tracking = false                    │
   └─────────────────────────────────────┘

Signal 变化时:
   ┌─────────────────────────────────────┐
   │ 通知所有依赖的 Effect                │
   │     ↓                               │
   │ 重新执行 Effect                     │
   └─────────────────────────────────────┘

3.3 条件依赖 #

依赖是动态追踪的:

jsx
function ConditionalDeps() {
  const [show, setShow] = createSignal(true);
  const [count, setCount] = createSignal(0);
  const [name, setName] = createSignal('Alice');

  createEffect(() => {
    if (show()) {
      // 当 show() 为 true 时,依赖 show 和 count
      console.log('Count:', count());
    } else {
      // 当 show() 为 false 时,只依赖 show
      console.log('Hidden');
    }
  });

  return (
    <div>
      <button onClick={() => setShow(s => !s)}>Toggle</button>
      <button onClick={() => setCount(c => c + 1)}>Count++</button>
    </div>
  );
}

四、响应式上下文 #

4.1 createRoot #

创建独立的响应式上下文:

jsx
import { createRoot, createSignal, createEffect } from 'solid-js';

createRoot((dispose) => {
  const [count, setCount] = createSignal(0);

  createEffect(() => {
    console.log('Count:', count());
  });

  setCount(1);

  // 清理响应式上下文
  // dispose();
});

4.2 createRoot 的用途 #

jsx
// 在组件外部创建响应式状态
const [globalCount, setGlobalCount] = createRoot(() => {
  return createSignal(0);
});

// 在任何地方使用
function ComponentA() {
  return <p>{globalCount()}</p>;
}

function ComponentB() {
  return (
    <button onClick={() => setGlobalCount(c => c + 1)}>
      Increment
    </button>
  );
}

4.3 清理机制 #

jsx
import { createRoot, createEffect, onCleanup } from 'solid-js';

const dispose = createRoot((dispose) => {
  const [count, setCount] = createSignal(0);

  createEffect(() => {
    const timer = setInterval(() => {
      console.log('Tick:', count());
    }, 1000);

    onCleanup(() => {
      clearInterval(timer);
      console.log('Cleaned up');
    });
  });

  return dispose;
});

// 清理所有响应式资源
dispose();

五、批量更新 #

5.1 自动批量 #

Solid 自动批量处理同步更新:

jsx
function BatchExample() {
  const [a, setA] = createSignal(1);
  const [b, setB] = createSignal(2);

  createEffect(() => {
    console.log('Effect:', a(), b());
  });

  const updateBoth = () => {
    setA(10);
    setB(20);
    // Effect 只执行一次,输出 "Effect: 10 20"
  };

  return (
    <button onClick={updateBoth}>Update Both</button>
  );
}

5.2 batch 函数 #

显式批量更新:

jsx
import { batch } from 'solid-js';

function BatchExample() {
  const [firstName, setFirstName] = createSignal('John');
  const [lastName, setLastName] = createSignal('Doe');

  const fullName = createMemo(() => `${firstName()} ${lastName()}`);

  createEffect(() => {
    console.log('Full name:', fullName());
  });

  const updateName = () => {
    batch(() => {
      setFirstName('Jane');
      setLastName('Smith');
    });
    // Memo 和 Effect 只计算/执行一次
  };

  return (
    <button onClick={updateName}>Update Name</button>
  );
}

六、响应式陷阱 #

6.1 解构丢失响应性 #

jsx
// 错误:解构后失去响应性
function BadExample() {
  const [user, setUser] = createSignal({ name: 'Alice', age: 25 });
  
  // 解构后不再是响应式的
  const { name, age } = user();
  
  return <p>{name}</p>; // 不会更新
}

// 正确:保持响应性
function GoodExample() {
  const [user, setUser] = createSignal({ name: 'Alice', age: 25 });
  
  return <p>{user().name}</p>; // 会更新
}

6.2 条件渲染中的 Signal #

jsx
// 错误:条件外读取 Signal
function BadExample() {
  const [show, setShow] = createSignal(false);
  const [count, setCount] = createSignal(0);
  
  const value = count(); // 在条件外读取
  
  return (
    <Show when={show()}>
      <p>{value}</p> // 不会响应 count 变化
    </Show>
  );
}

// 正确:在条件内读取 Signal
function GoodExample() {
  const [show, setShow] = createSignal(false);
  const [count, setCount] = createSignal(0);
  
  return (
    <Show when={show()}>
      <p>{count()}</p> // 会响应 count 变化
    </Show>
  );
}

6.3 异步中读取 Signal #

jsx
// 错误:异步中读取 Signal
function BadExample() {
  const [count, setCount] = createSignal(0);

  createEffect(() => {
    setTimeout(() => {
      console.log(count()); // 不会追踪依赖
    }, 1000);
  });
}

// 正确:同步读取后传递
function GoodExample() {
  const [count, setCount] = createSignal(0);

  createEffect(() => {
    const value = count(); // 同步读取
    setTimeout(() => {
      console.log(value); // 使用捕获的值
    }, 1000);
  });
}

七、响应式调试 #

7.1 使用 createEffect 调试 #

jsx
function DebugExample() {
  const [count, setCount] = createSignal(0);
  const [name, setName] = createSignal('Alice');

  // 调试:查看依赖变化
  createEffect(() => {
    console.log('Dependencies changed:');
    console.log('  count:', count());
    console.log('  name:', name());
  });

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count++</button>
      <button onClick={() => setName(n => n + '!')}>Name!</button>
    </div>
  );
}

7.2 使用 Solid DevTools #

  1. 安装浏览器扩展
  2. 打开开发者工具
  3. 查看 Signal 依赖图
  4. 监控状态变化

八、总结 #

8.1 响应式核心概念 #

概念 说明
Signal 响应式状态源
Memo 派生计算值
Effect 响应式副作用
依赖追踪 自动收集依赖
批量更新 合并多次更新

8.2 最佳实践 #

  1. 保持 Signal 调用:在需要响应性的地方调用 Signal
  2. 避免解构:直接访问 Signal 值
  3. 使用批量更新:多个状态更新时使用 batch
  4. 正确清理:使用 onCleanup 清理资源
  5. 调试技巧:使用 createEffect 和 DevTools

8.3 响应式原则 #

text
1. 状态变化 → 自动触发更新
2. 依赖追踪 → 精确更新
3. 批量更新 → 性能优化
4. 同步执行 → 可预测性
5. 细粒度控制 → 高效渲染
最后更新:2026-03-28