SolidJS / Qwik:零 JS 运行时与极致懒加载

1. 为什么关注"零运行时"(Zero/Minimal JS Runtime)与极致懒加载

痛点

  • 首屏 JS 体积与执行时间(Parse/Compile/Execute)过大,移动端受限更明显。
  • 传统 SSR + Hydration:整页级别注水,哪怕用户只与局部交互,也需要恢复整页组件树的事件与状态。
  • 构建时代码分拆(split)不等于运行时按需:路由级分包仍会加载大量非必要逻辑

目标

  • 以用户交互为中心的懒执行:不交互就不下载、不解析、不执行。
  • 最小可用 JS:运行时尽可能小,避免为框架本身支付额外成本。
  • 直达交互点 :状态与事件可以从 HTML 中直接恢复(可恢复性 Resumability),跳过全树 Hydration。

2. 核心理念对比:SolidJS vs Qwik

维度 SolidJS Qwik
运行时 极小、编译期优化 + 细粒度响应式,最小必要运行时 追求接近零运行时 首屏执行;通过 Resumability 实现按需恢复
响应模型 Signals + fine-grained reactivity(无虚拟 DOM diff) Signals + Resumable closure,事件与状态可从 HTML 直接恢复
Hydration 传统意义上仍需对交互区进行恢复,但粒度很细 跳过 Hydration,用户交互时才懒恢复相关岛屿代码
代码分拆 标准动态 import + 路由级 + 组件级按需 qrl(Qwik URLs) 将闭包序列化,到点下载、到点执行
生态 与 React 写法相近,学习成本低,兼容 TS/Vite 专属 Qwik City(路由/SSR),理念更前沿,收益极致

3. 关键技术点

3.1 Signals(信号)

  • 写法const [count, setCount] = createSignal(0)(SolidJS);const count = useSignal(0)(Qwik)
  • 优势:订阅粒度到具体表达式,不再对整颗组件树 diff;计算属性只在依赖变化时重算。

3.2 Fine-grained Reactivity(细粒度反应)

  • 由编译器/运行时建立精确依赖图,避免 VDOM diff 抖动。
  • SolidJS:以"signal → computation"图驱动更新。

3.3 Partial Hydration(局部注水)与 Islands 架构

  • 将页面拆成多个"岛",仅为交互岛注水。
  • 限制 :仍需在客户端下载与执行岛的 JS 以完成 Hydration。

3.4 Resumability(可恢复性,Qwik)

  • 将组件闭包与事件处理器序列化为 QRL(URL) ,把状态存在 DOM/HTML 中。
  • 首次交互时,框架定位目标处理器只下载那一小段代码并执行。
  • 避免整树 Hydration,首屏接近零 JS 执行。

4. SolidJS 实战:从 0 到 1

目标:构建一个"可交互排行榜"页面,首屏渲染快、更新粒度细。

4.1 初始化

使用 Vite 模板

js 复制代码
npm create vite@latest solid-demo -- --template solid-ts
cd solid-demo
npm i
npm run dev

4.2 Signals 与计算属性

js 复制代码
// src/components/Counter.tsx
import { createSignal, createMemo } from "solid-js";

export default function Counter() {
    const [count, setCount] = createSignal(0);
    const doubled = createMemo(() => count() * 2);
    return (
        <div>
            <p>count: {count()} / doubled: {doubled()}</p>
            <button onClick={() => setCount(c => c + 1)}>+1</button>
        </div>
    );
}

4.3 懒加载组件(基于路由/交互)

js 复制代码
// src/routes/index.tsx
import { lazy } from "solid-js";
const HeavyChart = lazy(() => import("../components/HeavyChart"));

export default function Home() {
    const [showChart, setShowChart] = createSignal(false);
    return (
        <main>
            <h1>SolidJS Zero-Overhead Demo</h1>
            <button onClick={() => setShowChart(true)}>加载图表</button>
            {showChart() && <HeavyChart />}
        </main>
    );
}
js 复制代码
import { onMount } from "solid-js";s
export default function HeavyChart() {

onMount(async () => {
    // 在组件内部再做二次懒加载(如第三方库)
    const { Chart } = await import("chart.js");
        // ...init chart
    });
    return <div id="chart" style={{ height: "240px" }} />;
}

4.4 SSR 与数据获取(可选)

  • SolidStart(Solid 官方应用框架)支持 SSR/路由/数据请求:
js 复制代码
npm create solid@latest solid-start-app
// src/routes/index.tsx(SolidStart)
import { createRouteData } from "solid-start";

export function routeData() {
    return createRouteData(async () => {
        const res = await fetch("/api/toplist");
        return res.json();
    });
}

export default function Page() {
    const data = routeData();

    return (
        <ul>
            {data()?.list.map((item: any) => (
                <li>{item.name} - {item.score}</li>
            ))}
        </ul>
    );

}

要点:Solid 的最小运行时 + 懒加载策略,能显著降低非必要 JS 的解析与执行成本。

5. Qwik 实战:从 0 到 1(含 Resumability)

目标:同样实现"可交互排行榜 + 重组件懒加载",但首屏不执行多余 JS,通过 Resumability 到点恢复。

5.1 初始化(Qwik City)

js 复制代码
npm create qwik@latest

选择 Qwik City + SSR

js 复制代码
cd qwik-app
npm i
npm run dev

5.2 Signals 与服务器端数据

js 复制代码
// src/routes/index.tsx
import { component$, useSignal, useVisibleTask$, routeLoader$ } from "@builder.io/qwik";

export const useTopList = routeLoader$(async () => {
    const list = await fetch("/api/toplist").then(r => r.json());
    return list;
});

export default component$(() => {
    const list = useTopList();
    const showChart = useSignal(false);

    return (
    <main>
        <h1>Qwik Resumability Demo</h1>
        <button onClick$={() => (showChart.value = true)}>加载图表</button>
        {showChart.value && <HeavyChart />}
        <ul>
            {list.value?.list?.map((it: any) => (
            <li>{it.name} - {it.score}</li>
        ))}
        </ul>
    </main>
    );
});

5.3 事件处理器与懒恢复(onClick$

  • 注意 onClick$ :Qwik 会把该处理器序列化为 QRL,首屏仅在 HTML 中标注链接。
  • 用户点击时,才下载执行对应模块,无需整页 Hydration

5.4 重组件按需下载(lazy via routeLoader$ + 动态 import)

js 复制代码
// src/components/HeavyChart.tsx
import { component$, useVisibleTask$ } from "@builder.io/qwik";

export default component$(() => {
    useVisibleTask$(async () => {
    const { Chart } = await import("chart.js");
        // 初始化图表逻辑...
    });
    return <div id="chart" style={{ height: "240px" }} />;
});
  • useVisibleTask$ :仅当组件在视口可见时才运行;结合滚动/折叠区可以实现更晚的懒执行

5.5 表单与行动(Action$)在边缘运行(可选)

js 复制代码
// src/routes/actions.tsx
import { component$, action$, z, Form } from "@builder.io/qwik-city";

export const useSubmitScore = action$(async ({ name, score }) => {
    // 运行在 server/edge,最小客户端 JS
    // ...持久化逻辑
    return { ok: true };
});

export default component$(() => {
    const submit = useSubmitScore();
    return (
        <Form action={submit}>
        <input name="name" />
        <input name="score" type="number" />
        <button type="submit">提交</button>
        {submit.value?.ok && <p>提交成功</p>}
        </Form>
    );
});

要点:Qwik 的 Resumability 使首屏近似"零 JS 执行",用户交互到哪就只恢复哪。

6. 懒加载策略与代码分拆清单

6.1 策略清单

  • 路由级 split:按页面维度拆包。
  • 组件级 lazy :对重组件(图表、编辑器、地图)做 lazy(() => import(...))
  • 可见即执行useVisibleTask$(Qwik) / 观察器(IntersectionObserver)延后到可见时。
  • 交互触发加载:按钮点击后再下载(示例中的 Chart.js)。
  • 数据延迟获取 :首屏仅渲染必要骨架,真实数据在空闲时(requestIdleCallback)或可见时获取。
  • 第三方 SDK 延时:社交/广告/监控 SDK 放在交互或后台空闲执行。

6.2 代码片段:通用可见懒执行(Solid 示例)

js 复制代码
import { onCleanup, onMount } from "solid-js";
export function onVisible(el: HTMLElement, cb: () => void) {
    const io = new IntersectionObserver((entries) => {
        entries.forEach((e) => {
            if (e.isIntersecting) {
                cb();
                io.disconnect();
            }
        });
    });
    io.observe(el);
    onCleanup(() => io.disconnect());
}

// 用法
// <div ref={el => onVisible(el, () => importHeavy()))} />

6.3 代码片段:按需加载第三方库(两端通用思想)

js 复制代码
async function loadLib<T = any>(url: string, globalVar: string): Promise<T> {
    if ((window as any)[globalVar]) return (window as any)[globalVar];
    await new Promise<void>((resolve, reject) => {
        const s = document.createElement('script');
        s.src = url; s.async = true;
        s.onload = () => resolve();
        s.onerror = reject;
        document.head.appendChild(s);
    });

    return (window as any)[globalVar] as T;
}

7. 总结与选型建议

  • 追求极致首屏 、大量内容站点、弱交互页面 → Qwik(Resumability) 更有优势。
  • 需要细粒度响应 + 运行时极小 ,又想快速上手 → SolidJS 是务实之选。
  • 不必二选一:Solid 用于复杂交互区,Qwik 用于内容站,或分项目落地。
相关推荐
卓伊凡3 小时前
苹果开发中什么是Storyboard?object-c 和swiftui 以及Storyboard到底有什么关系以及逻辑?优雅草卓伊凡
前端·后端
Devlive 开源社区3 小时前
CodeForge v25.0.3 发布:Web 技术栈全覆盖,编辑器个性化定制新时代
前端·编辑器
Charlo3 小时前
是什么让一个AI系统成为智能体(Agent)?
前端·后端
石小石Orz3 小时前
来自面试官给我的建议,我备受启发
前端·后端·面试
迪迦3 小时前
基于uni-app的校园综合服务平台开发实战
前端·javascript·开源·uniapp
一蓑烟雨,一任平生3 小时前
uni-app 实现做练习题(每一题从后端接口请求&切换动画&记录错题)
前端·javascript·html
狂炫一碗大米饭3 小时前
前端开发人员:以下是如何充分利用 Cursor😍😍😍
前端·cursor·trae
王六岁3 小时前
JavaScript作用域与作用域链深度解析
前端·javascript
我的写法有点潮3 小时前
一文教你搞懂sessionStorage、localStorage、cookie、indexedDB
前端·面试