Solid.js

技术栈
前端框架
reactivesignalsperformancejsxcompiled

概览

Solid.js 技术栈概览

Solid.js 是由 Ryan Carniato 创建的高性能声明式 UI 框架。它使用 JSX 语法但不同于 React——组件只执行一次,没有虚拟 DOM,通过**信号(Signals)**实现细粒度响应式更新,性能位列前端框架第一梯队。

Solid.js 是什么?

  • 编译时响应式框架(与 Svelte 类似理念)
  • JSX 语法 + 真正的响应式原语
  • 无虚拟 DOM,直接操作真实 DOM
  • 组件函数只执行一次(setup 阶段)

解决什么问题?

  • 追求极致性能的交互密集型应用
  • 需要 React 般 JSX 体验但不要虚拟 DOM 开销
  • 毕设中展示前沿技术、答辩加分

关键特性:

  • createSignal / createEffect / createMemo
  • 组件是真正的函数(非类)
  • SolidStart(SSR/SSG 元框架)
  • 原生 TypeScript 支持
  • 生态:Solid Router、Solid Primitives

安装

环境准备

  • 操作系统:macOS / Linux / Windows
  • Node.js:>= 16.x(推荐 20 LTS)
  • 包管理器:npm / yarn / pnpm
  • 编辑器:VS Code(推荐安装 SolidJS 语法支持插件)

安装命令

使用 Vite 模板(推荐)

# TypeScript 模板
npx degit solidjs/templates/ts my-solid-app
cd my-solid-app
npm install

# JavaScript 模板
npx degit solidjs/templates/js my-solid-app
cd my-solid-app
npm install

手动安装

mkdir my-solid-app &;& cd my-solid-app
npm init -y
npm install solid-js
npm install -D vite @babel/core vite-plugin-solid

vite.config.js

import { defineConfig } from "vite";
import solid from "vite-plugin-solid";

export default defineConfig({
  plugins: [solid()],
});

使用 SolidStart(全栈框架)

npm create solid@latest my-solid-app
# 选择 SolidStart + TypeScript

常见安装问题

1. 与 React 语法混淆

  • 说明:Solid 使用 JSX 但行为完全不同。createSignal 返回的是 getter/setter 函数而非值,必须作为函数调用
  • 解决:养成 count() 而非 count 的习惯

2. Vite 配置中 solid 插件缺失

  • 现象:JSX 不被识别
  • 解决:确保 vite-plugin-solid 在 plugins 数组中

3. TypeScript 类型报错

  • 解决:确保 tsconfig.json 包含 "jsx": "preserve""jsxImportSource": "solid-js"

4. 响应式不生效

  • 原因:最常见:在非追踪作用域外解构 signal(如 const { value } = props
  • 解决:在 createEffect 或 JSX 中直接使用 props.value,或用 mergeProps

示例

Solid.js Hello World — 响应式计数器

目标

展示 Solid.js 核心概念:Signal、Effect、JSX 与控制流组件。Solid 的 JSX 语法类似 React 但行为完全不同——组件只执行一次,Signal 是真正的细粒度响应式。

完整代码

// src/index.jsx
/* @refresh reload */
import { render } from "solid-js/web";
import App from "./App";

render(() => <App />, document.getElementById("root"));
// src/App.jsx
import { createSignal, createEffect, onCleanup } from "solid-js";
import { Show, For, Switch, Match } from "solid-js";

export default function App() {
  // createSignal 返回 [getter, setter]
  const [count, setCount] = createSignal(0);
  const [name, setName] = createSignal("朋友");
  const [todos, setTodos] = createSignal(["学 Solid.js", "写项目"]);
  const [newTodo, setNewTodo] = createSignal("");

  // 自动追踪依赖——count() 变化时执行
  createEffect(() => {
    console.log(`计数变了:${count()}`);
    document.title = `点击 ${count()} 次`;
  });

  // 定时器示例 + 清理
  const timer = setInterval(() => {
    // 不会重新执行组件!只更新这一个 signal
  }, 1000);
  onCleanup(() => clearInterval(timer));

  const addTodo = () => {
    if (newTodo().trim()) {
      setTodos([...todos(), newTodo()]);
      setNewTodo("");
    }
  };

  return (
    <main style={mainStyle}>
      <h1>⚛️ Solid.js Demo</h1>
      <p style={{ color: "#666" }}>
        Solid 的组件只渲染一次,但 Signal 保持细粒度更新
      </p>

      {/* 双向绑定:value={name()} + onInput 更新 signal */}
      <input
        style={inputStyle}
        value={name()}
        onInput={(e) => setName(e.currentTarget.value)}
        placeholder="你的名字..."
      />
      <p>
        你好,<strong>{name()}</strong>!
      </p>

      {/* 显示 signal 值——注意必须调用 () */}
      <h2 style={{ fontSize: "3rem", textAlign: "center" }}>{count()}</h2>

      <div style={{ textAlign: "center", margin: "16px 0" }}>
        <button style={btnStyle} onClick={() => setCount((c) => c + 1)}>
          + 1
        </button>
        <button style={btnStyle} onClick={() => setCount((c) => c - 1)}>
          - 1
        </button>
        <button style={{ ...btnStyle, background: "#64748b" }} onClick={() => setCount(0)}>
          重置
        </button>
      </div>

      {/* Show:条件渲染 */}
      <Show when={count() >= 10}>
        <div style={{ padding: 12, background: "#fef3c7", borderRadius: 8 }}>
          🎉 达到 {count()} 次!
        </div>
      </Show>

      {/* For:高效列表渲染 */}
      <h3>📋 待办事项</h3>
      <ul>
        <For each={todos()}>
          {(todo, index) => (
            <li>
              {index() + 1}. {todo}
              <button
                style={{ marginLeft: 8, color: "red", border: "none", cursor: "pointer" }}
                onClick={() => setTodos(todos().filter((_, i) => i !== index()))}
              >
                ✕
              </button>
            </li>
          )}
        </For>
      </ul>

      <div style={{ display: "flex", gap: 8 }}>
        <input
          style={{ flex: 1, ...inputStyle }}
          value={newTodo()}
          onInput={(e) => setNewTodo(e.currentTarget.value)}
          placeholder="新待办..."
          onKeyDown={(e) => e.key === "Enter" && addTodo()}
        />
        <button style={btnStyle} onClick={addTodo}>
          添加
        </button>
      </div>

      {/* Switch/Match:多分支条件 */}
      <div style={{ marginTop: 16, padding: 12, background: "#f0f0ff", borderRadius: 8 }}>
        <Switch>
          <Match when={count() === 0}>开始点击吧!</Match>
          <Match when={count() < 5}>继续加油~</Match>
          <Match when={count() < 10}>快突破 10 了!</Match>
          <Match when={true}>🔥 你已经是大佬了</Match>
        </Switch>
      </div>
    </main>
  );
}

// 样式
const mainStyle = {
  "max-width": "540px",
  margin: "40px auto",
  padding: "0 16px",
  "font-family": "-apple-system, 'Microsoft YaHei', sans-serif",
};
const inputStyle = {
  width: "100%",
  padding: "10px",
  border: "2px solid #e2e8f0",
  "border-radius": "8px",
  "font-size": "1rem",
  "box-sizing": "border-box",
};
const btnStyle = {
  padding: "10px 24px",
  border: "none",
  "border-radius": "8px",
  "font-size": "1rem",
  cursor: "pointer",
  background: "#7c4dff",
  color: "white",
};
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Solid.js App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="./src/index.jsx" type="module"></script>
  </body>
</html>

运行步骤

npm install
npm run dev     # Vite 开发服务器

预期输出

  • 计数器实时更新,不依赖虚拟 DOM diff
  • 输入框双向绑定 name signal
  • 待办列表可增删
  • Switch 组件根据计数显示不同消息
  • 页面标题随计数变化(createEffect)

Solid.js 响应式图书收藏

目标

用 Solid.js 实现图书收藏列表,展示 createSignal 响应式状态、createEffect 副作用和 For 控制流。

完整代码

src/index.jsx

import { render } from 'solid-js/web';
import App from './App';

render(() => <App />, document.getElementById('root'));

src/App.jsx

import { createSignal, createEffect, For, Show } from 'solid-js';
import styles from './App.module.css';

const INITIAL_BOOKS = [
  { id: 1, title: '深入理解计算机系统', author: 'Randal E. Bryant', year: 2016, read: true },
  { id: 2, title: '代码整洁之道', author: 'Robert C. Martin', year: 2010, read: false },
  { id: 3, title: '设计模式', author: 'GoF', year: 1994, read: true },
];

export default function App() {
  const [books, setBooks] = createSignal(INITIAL_BOOKS);
  const [newBook, setNewBook] = createSignal({ title: '', author: '', year: '' });
  const [search, setSearch] = createSignal('');

  // 派生状态:搜索结果
  const filteredBooks = () => {
    const term = search().toLowerCase();
    if (!term) return books();
    return books().filter(b =>
      b.title.toLowerCase().includes(term) ||
      b.author.toLowerCase().includes(term)
    );
  };

  // 副作用:统计已读数量
  createEffect(() => {
    const readCount = books().filter(b => b.read).length;
    console.log(`已读: ${readCount}/${books().length}`);
  });

  const addBook = (e) => {
    e.preventDefault();
    const b = newBook();
    if (!b.title || !b.author) return;
    setBooks(prev => [...prev, { ...b, id: Date.now(), read: false, year: Number(b.year) || 2024 }]);
    setNewBook({ title: '', author: '', year: '' });
  };

  const toggleRead = (id) => {
    setBooks(prev => prev.map(b => b.id === id ? { ...b, read: !b.read } : b));
  };

  const removeBook = (id) => {
    setBooks(prev => prev.filter(b => b.id !== id));
  };

  return (
    <div class={styles.app}>
      <h1>📚 图书收藏</h1>

      <input
        type="text"
        placeholder="搜索书名或作者..."
        value={search()}
        onInput={(e) => setSearch(e.target.value)}
        class={styles.search}
      />

      {/* 添加表单 */}
      <form onSubmit={addBook} class={styles.form}>
        <input
          placeholder="书名" value={newBook().title}
          onInput={(e) => setNewBook(p => ({ ...p, title: e.target.value }))}
        />
        <input
          placeholder="作者" value={newBook().author}
          onInput={(e) => setNewBook(p => ({ ...p, author: e.target.value }))}
        />
        <input
          placeholder="年份" type="number" value={newBook().year}
          onInput={(e) => setNewBook(p => ({ ...p, year: e.target.value }))}
        />
        <button type="submit">➕ 添加</button>
      </form>

      {/* 图书列表 */}
      <div class={styles.list}>
        <Show when={filteredBooks().length > 0} fallback={<p style="color:#999">没有匹配的图书</p>}>
          <For each={filteredBooks()}>
            {(book) => (
              <div class={`${styles.card} ${book.read ? styles.read : ''}`}>
                <div class={styles.info}>
                  <strong>{book.title}</strong>
                  <small>{book.author} · {book.year}</small>
                </div>
                <div class={styles.actions}>
                  <button onClick={() => toggleRead(book.id)}>
                    {book.read ? '✅ 已读' : '📖 标记已读'}
                  </button>
                  <button onClick={() => removeBook(book.id)} class={styles.del}>🗑️</button>
                </div>
              </div>
            )}
          </For>
        </Show>
      </div>

      <p class={styles.stats}>
        共 {books().length} 本 · 已读 {books().filter(b => b.read).length} 本
      </p>
    </div>
  );
}

运行步骤

npx degit solidjs/templates/js solid-books
cd solid-books
npm install
# 替换 src/App.jsx
npm run dev

预期输出

  • 展示 3 本预置图书,2 本标记已读(绿色)
  • 搜索框实时过滤图书
  • 添加新书后列表自动响应式更新
  • 控制台输出已读统计

教程

Solid.js 入门教程:细粒度响应式编程

一、Solid.js 是什么?

Solid.js 由 Ryan Carniato 创建,外观与 React 很像(JSX、组件、Hooks 风格 API),但底层机制完全不同:

  • 无虚拟 DOM:组件只执行一次,JSX 编译为真实 DOM 操作
  • 细粒度响应式:不是"状态变了 → 重新渲染组件",而是"状态变了 → 直接更新 DOM 中用到该状态的那个文本节点"
  • 性能极致:在 JS 框架 Benchmark 中长期排第一

核心哲学

"组件是组织的概念,不是渲染的概念"

二、Signal:最小响应单元

import { createSignal } from "solid-js";

// 返回 [getter, setter] 对
const [count, setCount] = createSignal(0);

count();        // 读取 → 自动追踪
setCount(1);    // 设置 → 通知所有依赖
setCount(c => c + 1); // 函数式更新

Signal vs React State

React useState Solid createSignal
存储位置 组件实例 闭包(独立于组件)
读取方式 count(值) count()(函数调用)
更新效果 组件重新渲染 仅更新依赖此 signal 的 DOM
可在组件外

三、Effect:副作用自动追踪

import { createEffect } from "solid-js";

createEffect(() => {
  // 自动追踪内部访问的所有 signal
  console.log(`新值:${count()}`);
  localStorage.setItem("count", String(count()));
  // 当且仅当 count() 变化时执行
});

Effect 的自动清理

createEffect(() => {
  const timer = setInterval(() => console.log("tick"), 1000);
  // Solid 自动追踪首次执行后的返回值
  onCleanup(() => clearInterval(timer));
  // 当 effect 重新执行或组件销毁时自动调用
});

四、派生状态:Memo

import { createMemo } from "solid-js";

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

// 仅在依赖变化时重新计算,否则返回缓存值
const total = createMemo(() => {
  console.log("重新计算 total");
  return items().reduce((sum, n) => sum + n, 0);
});

// 多次 total() 只会计算一次

五、控制流组件

Solid 用组件而非指令做条件/循环(因为组件只在需要时执行):

import { Show, Switch, Match, For, Index } from "solid-js";

<Show when={user()} fallback={<p>请登录</p>}>
  <Dashboard user={user()} />
</Show>

<Switch>
  <Match when={status() === "loading"}><Spinner /></Match>
  <Match when={status() === "error"}><Error message={error()} /></Match>
  <Match when={true}><Content /></Match>
</Switch>

{/* For:按值追踪,适合不可变列表 */}
<For each={todos()}>
  {(todo, index) => <li>{index()}: {todo.text}</li>}
</For>

{/* Index:按索引追踪,适合大量静态元素 */}
<Index each={photos()}>
  {(photo, index) => <img src={photo().url} />}
</Index>

六、生命周期

import { onMount, onCleanup } from "solid-js";

onMount(() => {
  // DOM 已挂载
  fetch("/api/data").then(/* ... */);
});

onCleanup(() => {
  // 组件销毁前
  // 取消订阅、清除定时器等
});

七、资源管理:createResource

import { createResource } from "solid-js";

const [userId, setUserId] = createSignal(1);

const [user, { mutate, refetch }] = createResource(userId, async (id) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
});

// user() → undefined(loading)或数据
// user.loading → true/false
// user.error → Error 或 undefined
// mutate(optimisticData) → 乐观更新

八、与 React 的关键区别

React Solid
useState = 组件级状态 createSignal = 独立于组件
useEffect = 依赖数组手动指定 createEffect = 自动追踪
useMemo / useCallback 手动优化 createMemo 自动缓存
组件 re-render = 函数重新执行 组件函数只执行一次
需要 key 做列表 diff For 按值追踪,无需 key

九、思考题

  1. 为什么 createSignal 可以在组件外部使用?这对状态管理意味着什么?
  2. 如果你在 createEffect 中不读取任何 signal,effect 会执行几次?
  3. Solid 的 ForIndex 有何区别?在什么场景该用哪个?

Solid.js 响应式原理与毕设应用

背景

Solid.js 是前端性能冠军。它使用 JSX 语法但组件函数只执行一次——没有虚拟 DOM,通过 Signals 实现真正的细粒度响应式。如果你的毕设需要展示最新技术或者需要极致性能(如数据可视化、实时表格),Solid 是答辩加分的利器。


核心概念

组件函数只执行一次

这是 Solid 和 React 最本质的区别:

// React:每次状态变化,整个函数重新执行
function Counter() {
  const [count, setCount] = useState(0);
  console.log('render');  // 每次 count 变化都会打印
  return <div>{count}</div>;
}

// Solid:函数只执行一次
function Counter() {
  const [count, setCount] = createSignal(0);
  console.log('setup');  // 只在初始化时打印一次
  return <div>{count()}</div>;  // count() 是 getter 函数
}

三大响应式原语

// Signal:单个响应式值
const [count, setCount] = createSignal(0);
count();        // 读取
setCount(1);    // 设置

// Effect:副作用
createEffect(() => {
  console.log('count changed:', count());
});

// Memo:派生值(缓存)
const double = createMemo(() => count() * 2);

分步操作

第一步:创建项目

npx degit solidjs/templates/js solid-app
cd solid-app
npm install
npm run dev

第二步:理解控制流

Solid 提供特殊的控制流组件:

import { For, Show, Switch, Match } from 'solid-js';

<Show when={user()} fallback={<p>请登录</p>}>
  <p>欢迎, {user().name}</p>
</Show>

<For each={students()}>
  {(student) => <li>{student.name}</li>}
</For>

第三步:数据获取

import { createResource } from 'solid-js';

async function fetchStudents() {
  const res = await fetch('/api/students');
  return res.json();
}

function StudentList() {
  const [students] = createResource(fetchStudents);
  return (
    <Switch>
      <Match when={students.loading}><p>加载中...</p></Match>
      <Match when={students.error}><p>加载失败</p></Match>
      <Match when={students()}>
        <For each={students()}>{(s) => <div>{s.name}</div>}</For>
      </Match>
    </Switch>
  );
}

毕设推荐场景

数据可视化大屏

Solid 的细粒度更新确保只有变化的数据点重渲染,比 React 高效得多。

实时协作应用

WebSocket 高频更新场景,Solid 的性能优势明显。

答辩加分项

用 Solid 并在答辩中讲清楚"无虚拟 DOM 的响应式原理",评委老师会有深刻印象。


思考题

  1. 为什么 Solid 组件函数只执行一次?它如何在状态变化时更新 DOM?
  2. Signal 返回的是 getter 函数而非值本身,这样设计的原因是什么?
  3. 对比 React 的 useMemo 和 Solid 的 createMemo,使用场景有何异同?

小结

Solid.js 代表了前端框架的新范式——编译时响应式 + 无虚拟 DOM。虽然生态不如 React,但毕设项目规模适中,用 Solid 不仅性能更优,技术话题性也更强。

参考资料

  1. [1] SolidJS Team. SolidJS Documentation. 2024. https://www.solidjs.com
  2. [2] Ryan Carniato. Building Reactive Apps with SolidJS. 2023.
  3. [3] Ryan Carniato. SolidJS vs React: Comparing Render Behavior. 2021.
  4. [4] Solid 团队. Solid.js 官方文档. 2024. https://www.solidjs.com/docs/latest
  5. [5] Solid 团队. Solid.js 教程. 2024. https://www.solidjs.com/tutorial
  6. [6] Ryan Carniato. Solid.js 性能揭秘. 2023. https://www.solidjs.com/blog/solidjs-the-performance-details