生命周期 #

一、生命周期概述 #

1.1 Solid 的生命周期理念 #

与 React 不同,Solid 没有传统的生命周期钩子(如 componentDidMount、useEffect)。Solid 使用响应式系统来处理生命周期相关的逻辑。

text
┌─────────────────────────────────────────────────────────────┐
│                    Solid vs React 生命周期                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   React                                                     │
│   ├── componentDidMount     → 组件挂载后                    │
│   ├── componentDidUpdate    → 组件更新后                    │
│   └── componentWillUnmount  → 组件卸载前                    │
│                                                             │
│   Solid                                                     │
│   ├── onMount               → 组件初始化时                  │
│   ├── createEffect          → 响应式更新                    │
│   └── onCleanup             → 清理资源                      │
│                                                             │
│   关键区别:                                                │
│   - Solid 组件只渲染一次                                   │
│   - 使用响应式系统处理更新                                 │
│   - 生命周期函数更简洁                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 生命周期函数 #

函数 说明
onMount 组件挂载时执行
onCleanup 组件卸载或 Effect 清理时执行
createEffect 响应式副作用

二、onMount #

2.1 基本用法 #

onMount 在组件首次渲染到 DOM 后执行一次。

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

function MyComponent() {
  onMount(() => {
    console.log('Component mounted');
  });

  return <div>Hello</div>;
}

2.2 DOM 访问 #

jsx
function InputFocus() {
  let inputRef;

  onMount(() => {
    // DOM 已挂载,可以安全访问
    inputRef.focus();
  });

  return (
    <input ref={inputRef} type="text" />
  );
}

2.3 数据获取 #

jsx
function UserProfile({ userId }) {
  const [user, setUser] = createSignal(null);
  const [loading, setLoading] = createSignal(true);

  onMount(async () => {
    try {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUser(data);
    } catch (error) {
      console.error('Failed to fetch user:', error);
    } finally {
      setLoading(false);
    }
  });

  return (
    <Show when={!loading()} fallback={<p>Loading...</p>}>
      <div>
        <h1>{user()?.name}</h1>
        <p>{user()?.email}</p>
      </div>
    </Show>
  );
}

2.4 第三方库初始化 #

jsx
function Chart({ data }) {
  let chartRef;
  let chart;

  onMount(() => {
    // 初始化图表库
    chart = new ChartLibrary(chartRef, {
      type: 'line',
      data: data
    });
  });

  onCleanup(() => {
    // 清理图表实例
    chart?.destroy();
  });

  return <div ref={chartRef} class="chart"></div>;
}

2.5 onMount 与 createEffect 的区别 #

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

  // onMount: 只执行一次
  onMount(() => {
    console.log('Mounted once');
  });

  // createEffect: 每次 count 变化都执行
  createEffect(() => {
    console.log('Count changed:', count());
  });

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

三、onCleanup #

3.1 基本用法 #

onCleanup 在组件卸载或 Effect 重新执行前执行。

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

function Timer() {
  const [seconds, setSeconds] = createSignal(0);

  onMount(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);

    onCleanup(() => {
      clearInterval(interval);
      console.log('Timer cleaned up');
    });
  });

  return <p>Seconds: {seconds()}</p>;
}

3.2 事件监听清理 #

jsx
function MouseTracker() {
  const [position, setPosition] = createSignal({ x: 0, y: 0 });

  onMount(() => {
    const handleMouseMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };

    window.addEventListener('mousemove', handleMouseMove);

    onCleanup(() => {
      window.removeEventListener('mousemove', handleMouseMove);
    });
  });

  return (
    <p>
      Position: {position().x}, {position().y}
    </p>
  );
}

3.3 在 Effect 中使用 #

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

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

    onCleanup(() => {
      clearTimeout(timer);
    });
  });

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

3.4 WebSocket 清理 #

jsx
function ChatRoom({ roomId }) {
  const [messages, setMessages] = createSignal([]);
  const [connected, setConnected] = createSignal(false);

  createEffect(() => {
    const ws = new WebSocket(`wss://example.com/rooms/${roomId()}`);

    ws.onopen = () => setConnected(true);
    ws.onclose = () => setConnected(false);
    ws.onmessage = (e) => {
      setMessages(prev => [...prev, JSON.parse(e.data)]);
    };

    onCleanup(() => {
      ws.close();
      setConnected(false);
    });
  });

  return (
    <div>
      <p>Status: {connected() ? 'Connected' : 'Disconnected'}</p>
      <ul>
        <For each={messages()}>
          {(msg) => <li>{msg.text}</li>}
        </For>
      </ul>
    </div>
  );
}

四、生命周期模式 #

4.1 初始化模式 #

jsx
function useInitialize(callback) {
  onMount(callback);
}

function MyComponent() {
  useInitialize(() => {
    console.log('Component initialized');
  });

  return <div>Content</div>;
}

4.2 资源管理模式 #

jsx
function useResource(fetchFn) {
  const [data, setData] = createSignal(null);
  const [loading, setLoading] = createSignal(true);
  const [error, setError] = createSignal(null);

  onMount(async () => {
    try {
      const result = await fetchFn();
      setData(result);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  });

  return { data, loading, error };
}

function UserProfile({ userId }) {
  const { data: user, loading, error } = useResource(
    () => fetch(`/api/users/${userId}`).then(r => r.json())
  );

  return (
    <Show when={!loading()} fallback={<p>Loading...</p>}>
      <Show when={!error()} fallback={<p>Error: {error()}</p>}>
        <h1>{user()?.name}</h1>
      </Show>
    </Show>
  );
}

4.3 订阅模式 #

jsx
function useSubscription(eventEmitter, eventName) {
  const [data, setData] = createSignal(null);

  onMount(() => {
    const handler = (newData) => setData(newData);
    eventEmitter.on(eventName, handler);

    onCleanup(() => {
      eventEmitter.off(eventName, handler);
    });
  });

  return data;
}

4.4 定时器模式 #

jsx
function useInterval(callback, delay) {
  onMount(() => {
    const id = setInterval(callback, delay);
    onCleanup(() => clearInterval(id));
  });
}

function Clock() {
  const [time, setTime] = createSignal(new Date());

  useInterval(() => setTime(new Date()), 1000);

  return <p>{time().toLocaleTimeString()}</p>;
}

五、实际应用 #

5.1 表单自动保存 #

jsx
function AutoSaveForm({ initialData, onSave }) {
  const [formData, setFormData] = createSignal(initialData);

  createEffect(() => {
    const timer = setTimeout(() => {
      onSave(formData());
    }, 1000);

    onCleanup(() => clearTimeout(timer));
  });

  return (
    <form>
      <input
        value={formData().name}
        onInput={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
      />
      <textarea
        value={formData().content}
        onInput={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
      />
    </form>
  );
}

5.2 媒体查询监听 #

jsx
function useMediaQuery(query) {
  const [matches, setMatches] = createSignal(
    window.matchMedia(query).matches
  );

  onMount(() => {
    const mediaQuery = window.matchMedia(query);
    const handler = (e) => setMatches(e.matches);

    mediaQuery.addEventListener('change', handler);
    onCleanup(() => mediaQuery.removeEventListener('change', handler));
  });

  return matches;
}

function ResponsiveComponent() {
  const isMobile = useMediaQuery('(max-width: 768px)');

  return (
    <div>
      {isMobile() ? 'Mobile View' : 'Desktop View'}
    </div>
  );
}

5.3 可见性检测 #

jsx
function useIntersectionObserver(ref, options = {}) {
  const [isIntersecting, setIsIntersecting] = createSignal(false);
  const [entry, setEntry] = createSignal(null);

  onMount(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        setIsIntersecting(entry.isIntersecting);
        setEntry(entry);
      },
      options
    );

    if (ref) {
      observer.observe(ref);
    }

    onCleanup(() => observer.disconnect());
  });

  return { isIntersecting, entry };
}

function LazyImage({ src, alt }) {
  let imgRef;
  const { isIntersecting } = useIntersectionObserver(
    () => imgRef,
    { threshold: 0.1 }
  );

  return (
    <img
      ref={imgRef}
      src={isIntersecting() ? src : ''}
      alt={alt}
      loading="lazy"
    />
  );
}

5.4 键盘快捷键 #

jsx
function useKeyboardShortcut(key, callback, modifiers = {}) {
  onMount(() => {
    const handler = (e) => {
      const matchCtrl = modifiers.ctrl ? e.ctrlKey : true;
      const matchShift = modifiers.shift ? e.shiftKey : true;
      const matchAlt = modifiers.alt ? e.altKey : true;

      if (e.key === key && matchCtrl && matchShift && matchAlt) {
        e.preventDefault();
        callback();
      }
    };

    window.addEventListener('keydown', handler);
    onCleanup(() => window.removeEventListener('keydown', handler));
  });
}

function Editor() {
  const [content, setContent] = createSignal('');

  useKeyboardShortcut('s', () => saveContent(content()), { ctrl: true });
  useKeyboardShortcut('z', () => undo(), { ctrl: true });

  return (
    <textarea
      value={content()}
      onInput={(e) => setContent(e.target.value)}
    />
  );
}

六、最佳实践 #

6.1 组织生命周期代码 #

jsx
function MyComponent() {
  // 1. 状态声明
  const [data, setData] = createSignal(null);

  // 2. 生命周期
  onMount(() => {
    // 初始化逻辑
    fetchData().then(setData);

    // 清理逻辑紧跟其后
    onCleanup(() => {
      // 清理资源
    });
  });

  // 3. Effects
  createEffect(() => {
    // 响应式逻辑
  });

  // 4. 渲染
  return <div>{data()}</div>;
}

6.2 避免内存泄漏 #

jsx
// 好的做法:总是清理资源
function GoodExample() {
  onMount(() => {
    const interval = setInterval(() => {}, 1000);
    const subscription = eventEmitter.subscribe(() => {});

    onCleanup(() => {
      clearInterval(interval);
      subscription.unsubscribe();
    });
  });
}

// 不好的做法:忘记清理
function BadExample() {
  onMount(() => {
    setInterval(() => {}, 1000); // 内存泄漏
  });
}

6.3 条件性清理 #

jsx
function ConditionalCleanup() {
  let resource = null;

  onMount(() => {
    if (someCondition) {
      resource = createResource();
    }

    onCleanup(() => {
      if (resource) {
        resource.destroy();
      }
    });
  });
}

七、总结 #

7.1 生命周期函数 #

函数 触发时机 用途
onMount 组件挂载后 初始化、数据获取
onCleanup 组件卸载/Effect 清理 资源清理
createEffect 依赖变化时 响应式副作用

7.2 使用场景 #

场景 推荐使用
DOM 操作 onMount
数据获取 onMount 或 createResource
事件监听 onMount + onCleanup
定时器 onMount + onCleanup
响应式更新 createEffect

7.3 最佳实践 #

  1. 资源清理:始终在 onMount 中注册 onCleanup
  2. 组织代码:按状态、生命周期、Effects、渲染顺序组织
  3. 避免泄漏:清理所有订阅、定时器、事件监听
  4. 使用 Hooks:封装可复用的生命周期逻辑
最后更新:2026-03-28