Signal信号 #

一、Signal 基础 #

1.1 什么是 Signal #

Signal 是 Solid 中最基础的响应式原语,用于存储和管理可变状态。

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

// 创建 Signal
const [count, setCount] = createSignal(0);

// count() - 读取值(getter)
// setCount() - 设置值(setter)

1.2 Signal 结构 #

jsx
const [getter, setter] = createSignal(initialValue);

// getter - 读取函数
// setter - 设置函数
text
┌─────────────────────────────────────────────────────────────┐
│                    Signal 结构                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   createSignal(0)                                           │
│         │                                                   │
│         ├──→ getter (count)                                 │
│         │    └── 调用 count() 返回当前值                    │
│         │    └── 自动追踪依赖                               │
│         │                                                   │
│         └──→ setter (setCount)                              │
│              ├── setCount(5) 设置新值                       │
│              └── setCount(prev => prev + 1) 基于前值更新    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、创建 Signal #

2.1 基本类型 #

jsx
// 数字
const [count, setCount] = createSignal(0);

// 字符串
const [name, setName] = createSignal('Alice');

// 布尔值
const [isActive, setIsActive] = createSignal(false);

// null 和 undefined
const [user, setUser] = createSignal(null);
const [data, setData] = createSignal(undefined);

2.2 对象类型 #

jsx
// 对象
const [user, setUser] = createSignal({
  name: 'Alice',
  age: 25,
  email: 'alice@example.com'
});

// 数组
const [items, setItems] = createSignal([1, 2, 3]);

// Map 和 Set
const [map, setMap] = createSignal(new Map());
const [set, setSet] = createSignal(new Set());

2.3 带选项的 Signal #

jsx
// 自定义 equals 函数
const [value, setValue] = createSignal(0, {
  equals: (prev, next) => prev === next
});

// 禁用相等性检查
const [items, setItems] = createSignal([], {
  equals: false
});

// 自定义比较逻辑
const [user, setUser] = createSignal({ id: 1, name: 'Alice' }, {
  equals: (prev, next) => prev.id === next.id
});

三、读取 Signal #

3.1 基本读取 #

jsx
const [count, setCount] = createSignal(0);

// 读取值
console.log(count()); // 0

// 在 JSX 中使用
function Counter() {
  return <p>Count: {count()}</p>;
}

3.2 响应式读取 #

在响应式上下文中读取会自动追踪依赖:

jsx
const [count, setCount] = createSignal(0);

// 在 createEffect 中读取 - 自动追踪
createEffect(() => {
  console.log('Count:', count());
});

// 在 createMemo 中读取 - 自动追踪
const doubled = createMemo(() => count() * 2);

// 在组件 JSX 中读取 - 自动追踪
function Counter() {
  return <p>Count: {count()}</p>;
}

3.3 非响应式读取 #

使用 untrack 避免依赖追踪:

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

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

createEffect(() => {
  // count 被追踪
  console.log('Count:', count());
  
  // name 不被追踪
  console.log('Name:', untrack(() => name()));
});

// 只有 count 变化时 Effect 才会重新执行

四、更新 Signal #

4.1 直接设置 #

jsx
const [count, setCount] = createSignal(0);

// 直接设置新值
setCount(5);
console.log(count()); // 5

// 设置为任意值
setCount(100);
setCount(0);

4.2 基于前值更新 #

jsx
const [count, setCount] = createSignal(0);

// 使用函数更新
setCount(prev => prev + 1);
console.log(count()); // 1

setCount(prev => prev * 2);
console.log(count()); // 2

4.3 对象更新 #

jsx
const [user, setUser] = createSignal({
  name: 'Alice',
  age: 25
});

// 更新单个属性
setUser(prev => ({ ...prev, age: 26 }));

// 更新多个属性
setUser(prev => ({
  ...prev,
  name: 'Bob',
  age: 30
}));

// 完全替换
setUser({ name: 'Charlie', age: 35 });

4.4 数组更新 #

jsx
const [items, setItems] = createSignal([1, 2, 3]);

// 添加元素
setItems(prev => [...prev, 4]);

// 删除元素
setItems(prev => prev.filter(item => item !== 2));

// 更新元素
setItems(prev => prev.map(item => item * 2));

// 清空数组
setItems([]);

五、Signal 选项 #

5.1 equals 选项 #

控制何时触发更新:

jsx
// 默认:值相等时不触发更新
const [count, setCount] = createSignal(0);
setCount(0); // 不触发更新(值相同)

// 禁用相等性检查:每次都触发更新
const [items, setItems] = createSignal([1, 2, 3], {
  equals: false
});
setItems([1, 2, 3]); // 触发更新(即使内容相同)

// 自定义比较函数
const [user, setUser] = createSignal(
  { id: 1, name: 'Alice' },
  { equals: (prev, next) => prev?.id === next?.id }
);
setUser({ id: 1, name: 'Bob' }); // 不触发更新(id 相同)

5.2 internal 选项 #

创建内部 Signal(不触发 DevTools 更新):

jsx
const [internal, setInternal] = createSignal(0, {
  internal: true
});

六、高级用法 #

6.1 计算属性模式 #

jsx
function createUserStore(initialUser) {
  const [user, setUser] = createSignal(initialUser);

  return {
    // 只读访问
    get name() { return user().name; },
    get age() { return user().age; },
    get email() { return user().email; },
    
    // 更新方法
    updateName(name) {
      setUser(prev => ({ ...prev, name }));
    },
    updateAge(age) {
      setUser(prev => ({ ...prev, age }));
    }
  };
}

const userStore = createUserStore({
  name: 'Alice',
  age: 25,
  email: 'alice@example.com'
});

// 使用
console.log(userStore.name);
userStore.updateName('Bob');

6.2 带验证的 Signal #

jsx
function createValidatedSignal(initialValue, validator) {
  const [value, setValue] = createSignal(initialValue);
  const [error, setError] = createSignal(null);

  const setValidatedValue = (newValue) => {
    const result = validator(newValue);
    if (result.valid) {
      setValue(newValue);
      setError(null);
    } else {
      setError(result.error);
    }
  };

  return {
    value,
    error,
    setValue: setValidatedValue
  };
}

// 使用
const email = createValidatedSignal('', (value) => {
  if (!value) return { valid: false, error: 'Email is required' };
  if (!value.includes('@')) return { valid: false, error: 'Invalid email' };
  return { valid: true };
});

6.3 带历史记录的 Signal #

jsx
function createSignalWithHistory(initialValue, maxHistory = 10) {
  const [value, setValue] = createSignal(initialValue);
  const [history, setHistory] = createSignal([initialValue]);
  const [index, setIndex] = createSignal(0);

  const setValueWithHistory = (newValue) => {
    setValue(newValue);
    setHistory(prev => [...prev.slice(0, index() + 1), newValue].slice(-maxHistory));
    setIndex(prev => Math.min(prev + 1, maxHistory - 1));
  };

  const undo = () => {
    if (index() > 0) {
      setIndex(prev => prev - 1);
      setValue(history()[index() - 1]);
    }
  };

  const redo = () => {
    if (index() < history().length - 1) {
      setIndex(prev => prev + 1);
      setValue(history()[index() + 1]);
    }
  };

  return {
    value,
    setValue: setValueWithHistory,
    undo,
    redo,
    canUndo: () => index() > 0,
    canRedo: () => index() < history().length - 1
  };
}

6.4 持久化 Signal #

jsx
function createPersistentSignal(key, initialValue) {
  const stored = localStorage.getItem(key);
  const [value, setValue] = createSignal(
    stored ? JSON.parse(stored) : initialValue
  );

  const setAndPersist = (newValue) => {
    setValue(newValue);
    localStorage.setItem(key, JSON.stringify(newValue));
  };

  return [value, setAndPersist];
}

// 使用
const [theme, setTheme] = createPersistentSignal('theme', 'light');
const [todos, setTodos] = createPersistentSignal('todos', []);

七、Signal 与组件 #

7.1 组件内 Signal #

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

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

7.2 组件间共享 Signal #

jsx
// stores/counter.js
import { createSignal } from 'solid-js';

export const [count, setCount] = createSignal(0);

// components/Counter.jsx
import { count, setCount } from '../stores/counter';

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

// components/Display.jsx
import { count } from '../stores/counter';

export function Display() {
  return <p>Current count: {count()}</p>;
}

7.3 通过 Props 传递 Signal #

jsx
function Parent() {
  const [count, setCount] = createSignal(0);

  return (
    <div>
      <Child count={count} setCount={setCount} />
    </div>
  );
}

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

八、性能优化 #

8.1 避免不必要的更新 #

jsx
// 使用 equals 选项
const [data, setData] = createSignal([], {
  equals: (prev, next) => {
    if (prev.length !== next.length) return false;
    return prev.every((item, i) => item.id === next[i].id);
  }
});

8.2 使用 batch 批量更新 #

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

function updateMultiple() {
  batch(() => {
    setFirstName('John');
    setLastName('Doe');
    setAge(30);
  });
}

8.3 延迟更新 #

jsx
function useDebouncedSignal(initialValue, delay = 300) {
  const [value, setValue] = createSignal(initialValue);
  const [debouncedValue, setDebouncedValue] = createSignal(initialValue);
  let timeout;

  const setDebounced = (newValue) => {
    setValue(newValue);
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      setDebouncedValue(newValue);
    }, delay);
  };

  return [value, setDebounced, debouncedValue];
}

九、总结 #

9.1 Signal 核心 API #

API 说明
createSignal(initial) 创建 Signal
getter() 读取值
setter(value) 设置值
setter(prev => new) 基于前值更新

9.2 Signal 选项 #

选项 说明
equals 自定义相等性比较
internal 内部 Signal

9.3 最佳实践 #

  1. 使用函数更新:基于前值更新时使用函数形式
  2. 不可变更新:对象和数组使用展开运算符
  3. 合理使用 equals:优化不必要的更新
  4. 批量更新:多个更新使用 batch
  5. 避免解构:保持 Signal 的响应性
最后更新:2026-03-28