Published on 2025-03-13

SolidJS 核心概念带来的开发最佳实践

全栈开发赋予我们一致且高效的开发流程,同时显著降低了开发成本。学习和使用 SolidJS 进行开发,除了享受其带来的卓越性能外,我们自然也应关注开发效率、开发体验以及成本下降带来的综合收益。本文将基于 SolidJS 的开发哲学,梳理出一套开发流程的最佳实践,帮助开发者更高效地构建应用。由于个人水平有限,若有不准确之处,欢迎批评指正。

成本下降

成本下降的原理显而易见:更高的性能意味着在相同访问量下,资源消耗更少。随着 Serverless 服务逐渐成为主流,这种优势能够直接转化为成本的降低。例如,更少的计算资源占用意味着更低的云服务费用。

开发效率与体验

提升开发效率和体验则需要深入理解 SolidJS 的响应式编程模型及其开发思想。通过合理的实践,我们可以让代码更清晰、开发更高效。以下是具体的实践步骤:

1. 厘清需要响应式渲染的变量

SolidJS 的核心是响应式系统,因此第一步是分析并确定哪些变量是 DOM 渲染所依赖的。进一步,我们需要明确这些变量的来源和特性:

  • 本地 vs. 服务器:变量是客户端本地生成的,还是从服务器获取的?
  • 同步 vs. 异步:变量是同步计算的,还是需要异步加载?
  • 组件内 vs. 跨组件:变量仅在当前组件内使用,还是需要跨组件共享?

示例

// 本地同步变量
const [count, setCount] = createSignal(0);

// 服务器异步变量
const data = createAsync(async () => {
  const res = await fetch("/api/data");
  return await res.json();
});

明确这些特性后,我们才能为变量选择合适的响应式工具和数据管理方式。

2. 确定各个变量的修改方案

变量的修改方式取决于其类型和用途:

  • 普通 Signal:使用 setter 直接修改,例如 setCount
  • 异步 Signal:通常是从服务器获取的数据,使用 createAsync 读取,并通过 action 修改服务器端数据。

修改响应式变量的时机通常分为两种:

  1. 触发条件:用户交互或其他事件触发时,通过 setter 修改。
  2. 副作用:依赖其他 Signal 的变化,在 createEffect 中更新。通常用于处理异步数据更新后的后续逻辑。

示例

// 触发条件修改
<button onClick={() => setCount(count() + 1)}>增加</button>

// 副作用修改
createEffect(() => {
  const serverData = data();
  if (serverData) {
    setLocalData(processData(serverData));
  }
});

3. 确定变量响应的时机

SolidJS 的响应式系统默认是同步触发的,但在某些场景下,我们需要控制响应的行为:

  • 取消响应性:在 Optimistic UI(乐观更新)场景中,可能不希望服务器数据的变更立即触发渲染。此时可用 untrack 取消响应性。
  • 缓存响应:使用 createMemo 创建缓存值,仅在依赖变化时重新计算,避免不必要的性能开销。
  • 批量修改:通过 batch 将多个 Signal 修改打包执行,避免逐一更新导致的页面闪烁。

示例

// 取消响应性
const staticData = untrack(() => data());

// 缓存响应
const memoizedValue = createMemo(() => computeExpensiveValue(count()));

// 批量修改
batch(() => {
  setCount(count() + 1);
  setOtherSignal(newValue);
});

4. 执行时机

SolidStart 作为 SolidJS 的全栈框架,带来了一些独特的执行时机考虑:

  • Server Function:在 SolidStart 中,服务器函数通过在函数体第一行添加 "use server" 标记来定义。这种函数仅在服务器端执行,其代码不会打包到客户端,确保安全性和数据流一致性。
  • 水合与 HTML 渲染:注意客户端独有功能(如 localStorage)仅在水合后执行,避免服务器端渲染出错。
  • 流式 SSR:SolidStart 默认支持流式服务器端渲染(SSR)。在处理 HTTP Header(如传递 token 或设置加密 session)时,需使用 await 阻塞式调用,确保在 Header 返回前完成逻辑。

示例

// Server Function 定义
async function fetchData() {
  "use server";
  return await db.query("SELECT * FROM items");
}

// 在组件中使用
const data = createAsync(() => fetchData());

总结

通过梳理哪些数据需要流动、在何时流动、以及如何修改这些数据,我们可以让开发过程更加清晰高效。即使无法完全理清所有细节,遵循这一思考框架也能显著提升开发效率。更重要的是,这种方法论对其他语言或框架的开发也具有借鉴意义。

接下来,我们将通过创建应用的实践方式来正式进入 SolidStart 应用的开发教程,敬请期待!

built byHannus, a former statistician