编程 Svelte 5 编译时革命:用 Runes 重塑响应式,从虚拟 DOM 到细粒度更新的范式迁移(2026 实战指南)

2026-08-15 02:43:54 +0800 CST views 7

Svelte 5 编译时革命:用 Runes 重塑响应式,从虚拟 DOM 到细粒度更新的范式迁移(2026 实战指南)

2026 年的前端圈有一个尴尬的事实:我们写了十几年的"声明式 UI",但每一次状态变更,浏览器都在替我们做一场昂贵的"虚拟 DOM diff 猜谜游戏"。React 用协调(Reconciliation)兜底,Vue 用依赖追踪兜底,而 Svelte 选择了一条更激进的路——把响应式计算彻底搬到编译期,运行时只留最薄的胶水代码。Svelte 5 带来的 Runes(符文)不是语法糖,而是一场范式迁移。本文从运行时税的本质讲起,拆解编译时响应式的原理、Svelte 编译器的工作方式,并配一套完整的可运行实战,最后给出性能基准与 15 条生产级实践。


一、背景介绍:前端框架的"运行时税"

1.1 虚拟 DOM 是解药,也是税

2013 年 React 把"UI 是状态的函数"这件事讲明白了:UI = f(state)。为了不让开发者手动操作 DOM,React 引入了 Virtual DOM:每次状态变化,先算出一棵新的虚拟树,再和旧的虚拟树做 diff,最后只把差异 patch 到真实 DOM 上。

这套设计拯救了无数被 jQuery 时代命令式 DOM 操作折磨的程序员,但它有一个被长期忽视的代价——运行时税(runtime tax)

  • 每次渲染都要创建整棵 VNode 树(即便大部分节点没变);
  • diff 算法无论如何都要遍历组件树(即使只是改了一个计数器);
  • 为了"通用性",运行时必须携带一套协调器、调度器、fiber 树。
// React:每次 state 变化,组件函数整体重新执行
function Counter() {
  const [count, setCount] = useState(0);
  // 哪怕只改了 count,整个函数体、所有 JSX 都会被重新求值
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

注意,上面的 count + 1 触发的是整组件重渲染。React 靠 memo / useMemo / useCallback 来手动修剪这棵树的更新范围——也就是说,开发者要自己为运行时税买单。

1.2 三条主流路线

到 2026 年,主流框架在"如何少交运行时税"上分成了三条路线:

框架响应式策略更新粒度心智负担
React 19整组件重渲染 + 手动 memo组件级高(要懂 memo 陷阱)
Vue 3运行时依赖追踪(Proxy)字段级中(自动追踪)
Svelte 5编译期依赖图 + 细粒度 signal字段级低(编译器生成)
SolidJS编译期 + 类 MobX signal字段级

Vue 3 用 Proxy 在运行时拦截对象访问来建立依赖关系,所以它仍需在运行时携带响应式系统。Svelte 5 更进一步:在编译期就静态分析出谁是依赖、谁是被依赖者,直接生成"当 A 变时更新 B"的精确命令式代码,运行时几乎不需要一个"响应式引擎"。

这就是"编译时框架"的核心主张:能编译期确定的事,绝不拖到运行时

1.3 为什么是现在

Svelte 5 于 2024 年底正式发布,但到 2026 年它才真正成为生产级首选,原因有三:

  1. Runes 统一了 Svelte 4 的"隐式响应式"——过去 Svelte 靠变量赋值自动追踪,但跨组件、跨模块的响应式很别扭;Runes 把响应式变成显式、可组合的"符文",彻底解决了模块化难题。
  2. Signal 范式在业界达成共识——SolidJS、Vue Vapor、Preact Signals、甚至 React 社区都在向细粒度 signal 靠拢,Svelte 5 的 Runes 是其中最优雅的实现之一。
  3. 构建工具链成熟——Vite 6/7 + SvelteKit 2 让编译时框架的工程化成本降到极低。

二、核心概念:Runes 与编译时响应式

2.1 什么是 Runes(符文)

Runes 是 Svelte 5 引入的一组以 $ 开头的"编译器指令"。它们看起来像函数,但不是运行时函数——它们是写给编译器的"提示",编译器据此生成精确更新代码。核心 Runes:

  • $state:声明响应式状态
  • $derived:声明派生状态(依赖其他状态自动重算)
  • $effect:声明副作用(状态变化时自动执行)
  • $props:声明组件入参
  • $bindable:声明可双向绑定的 prop
  • $inspect:调试响应式依赖
  • $host:在自定义元素中获取宿主

2.2 $state:从"变量"到"信号"

Svelte 4 时代,顶层 let 就是响应式的;Svelte 5 用 $state 显式声明:

<script>
  // Svelte 5:显式声明响应式状态
  let count = $state(0);

  function increment() {
    count += 1; // 编译器会把这行变成"通知所有依赖 count 的订阅者"
  }
</script>

<button onclick={increment}>
  点击了 {count} 次
</button>

关键差异:在 Svelte 4 里,count 是一个普通变量,编译器靠"赋值语句出现在组件作用域"来猜测响应式;在 Svelte 5 里,$state 返回一个信号(signal)的访问器,读取即订阅、写入即通知,语义精确且可跨模块复用。

深层对象同样响应式,且是**深层(deep)**的:

<script>
  let user = $state({
    name: '三哥',
    profile: { age: 30, tags: ['go', 'rust'] }
  });

  // 任意深度的赋值都会触发精确更新
  function growUp() {
    user.profile.age += 1;
    user.profile.tags.push('svelte');
  }
</script>

编译器会为 user.profile.age 这类深层路径生成独立的更新函数,而不是"整体替换 user 对象"。这是 Svelte 5 比 Vue 3 的 reactive() 更省事的地方——你不用 toRefs、不用 .value,深层嵌套天然响应式。

2.3 $derived:声明式派生,告别冗余状态

派生状态是前端最常见的需求:根据 A、B 算出 C。传统写法要么在渲染时现算(每次都重算),要么自己手动同步(易错)。

<script>
  let price = $state(100);
  let quantity = $state(2);

  // 派生值:只有当 price / quantity 真正变化时才重算
  let total = $derived(price * quantity);

  // 也可以写带语句块的派生(适合多步计算)
  let summary = $derived.by(() => {
    const t = price * quantity;
    const discount = t > 200 ? 0.9 : 1;
    return { raw: t, final: t * discount };
  });
</script>

<p>小计:{total},折后:{summary.final}</p>

$derived 的本质是记忆化(memoization)的信号:它内部维护一个脏标记,只有依赖项变化时才会重新执行计算函数。这和 React 的 useMemo 目的一样,但 React 需要你手动列依赖数组(且依赖数组是"建议性"的,漏写也不报错),而 $derived 的依赖是编译器静态分析出来的,不存在"漏写依赖"的 bug。

2.4 $effect:副作用的精确编排

副作用(发请求、操作 DOM、订阅事件)在 React 里用 useEffect,依赖数组同样容易写错。Svelte 5 的 $effect 自动追踪内部读取的响应式状态:

<script>
  let query = $state('');
  let results = $state([]);

  // 只要 query 变化,这个 effect 就重新运行
  $effect(() => {
    const controller = new AbortController();
    fetch(`/api/search?q=${query}`, { signal: controller.signal })
      .then(r => r.json())
      .then(data => results = data);
    return () => controller.abort(); // 清理函数,query 下次变化前执行
  });
</script>

对比 React:

// React:依赖数组必须手动维护,漏写 query 就是 bug
useEffect(() => {
  const controller = new AbortController();
  fetch(`/api/search?q=${query}`, { signal: controller.signal })
    .then(r => r.json())
    .then(setResults);
  return () => controller.abort();
}, [query]); // ← 忘了写 query?静默 bug

$effect 还有一个关键特性:初始挂载时一定会跑一次(类似 useEffect 不加依赖的行为),但后续只在真正被读取的响应式值变化时跑。编译器会分析 effect 函数体内读取了哪些 $state/$derived,自动建立追踪。

⚠️ 实战坑:$effect 里读到的"非响应式"变量不会触发重跑。如果你在 effect 里用了普通 let,改它不会重跑 effect——这是预期行为,但新人常踩。

2.5 $props$bindable:组件通信的显式化

Svelte 4 的 props 是"解构即声明",隐式且不利于类型推断。Svelte 5 用 $props()

<!-- Child.svelte -->
<script>
  let { name, age = 18, onchange } = $props();
</script>

<p>{name} 今年 {age} 岁</p>
<button onclick={() => onchange?.(age + 1)}>长大</button>

双向绑定(以前靠 bind: 语法,跨组件很绕)现在用 $bindable

<!-- Parent.svelte -->
<script>
  import Child from './Child.svelte';
  let username = $state('');
</script>

<!-- 子组件内部改 username,父组件的 $state 同步更新 -->
<Child bind:name={username} />

<!-- Child.svelte -->
<script>
  let { name = $bindable('') } = $props();
  // 在子组件里写 name = '新值' 会同步回父组件
</script>
<input bind:value={name} />

$bindable 是 Svelte 5 解决"跨组件双向绑定"的优雅方案——它把"这个 prop 可以反向流入父级"显式标记出来,类型系统(svelte-check)能直接推断出可绑定性。

2.6 类组件:把状态藏进 class

Svelte 5 另一个重磅能力:$state 可以用在 class 的字段上,让"状态 + 行为"封装成真正的面向对象单元,而不是散落在 <script> 顶层的变量:

<script>
  class TodoStore {
    todos = $state([]);
    filter = $state('all');

    get visible() {
      // getter 里的 $state 读取也会被追踪
      if (this.filter === 'all') return this.todos;
      return this.todos.filter(t => t.done === (this.filter === 'done'));
    }

    add(text) {
      this.todos = [...this.todos, { id: crypto.randomUUID(), text, done: false }];
    }

    toggle(id) {
      this.todos = this.todos.map(t => t.id === id ? { ...t, done: !t.done } : t);
    }
  }

  const store = new TodoStore();
</script>

{#each store.visible as todo (todo.id)}
  <li onclick={() => store.toggle(todo.id)}>
    {todo.done ? '✅' : '⬜'} {todo.text}
  </li>
{/each}

这种写法让 Svelte 5 可以轻松对接外部状态库(比如把 store 抽成独立 .svelte.ts 模块,全局共享),也顺手解决了 Svelte 4 时代"store 必须用 writable() + $ 前缀订阅"的繁琐语法。


三、架构分析:Svelte 编译器到底做了什么

3.1 编译管线总览

Svelte 不是运行时库,它是编译器。一个 .svelte 文件经过以下步骤变成普通 JS:

.svelte 源文件
   │
   ├─ 1. 解析(Parser):拆成 template(HTML 片段)+ instance(<script>)+ module(<script module>)
   │
   ├─ 2. 静态分析(Analyzer):扫描所有 $state/$derived/$effect/$props,
   │      构建"响应式依赖图"(谁读谁、谁写谁)
   │
   ├─ 3. 代码生成(Codegen):
   │      - template 中的 {count} 变成 "当 count 变时,替换这个文本节点的 nodeValue"
   │      - $derived 变成带脏标记的 getter
   │      - $effect 变成"注册到 effect 队列 + 自动依赖收集"
   │
   └─ 4. 输出:一个 create_fragment / mount / unmount 的命令式组件对象

关键点:模板里的 {count} 不会触发整组件重渲染,编译器直接生成"更新这一个文本节点"的代码。这就是"细粒度更新"——更新范围精确到 DOM 节点,而不是组件。

3.2 依赖图是静态的,不是运行时的

以这段为例:

<script>
  let a = $state(1);
  let b = $state(2);
  let sum = $derived(a + b);
</script>
<p>{sum}</p>

编译器在编译期就知道:sum 依赖 ab<p> 的文本依赖 sum。生成的伪代码类似:

// 编译器生成的(概念性)代码
function mount() {
  const p = document.createElement('p');
  // 注册:当 sum 变化,更新 p 的文本
  $.effect(() => { p.textContent = sum.get(); });
}

// sum 的派生:脏标记驱动
const sum = $.derived(() => a.get() + b.get());

运行时只做"被通知后更新",不需要遍历任何树、不需要 diff。对比 React:哪怕你用 useMemo 优化了重渲染,React 仍然要跑一次 fiber 协调来确定"哪些节点要更新"——这步开销 Svelte 完全没有。

3.3 和 React Compiler 是同一回事吗?

2025 年 React 推出了 React Compiler(原 React Forget),它也是在编译期自动插入 memo/useMemo,减少无效重渲染。很多人问:"那 Svelte 5 和 React Compiler 区别在哪?"

本质区别在粒度与残留运行时

维度React CompilerSvelte 5
编译目标自动加 memo,仍走 VDOM diff直接生成命令式更新,无 VDOM
运行时残留完整 React 运行时 + fiber极薄运行时(信号原语)
更新粒度组件级(仍要 diff)节点级(无 diff)
包体积小(编译产物自带逻辑,库本身小)

React Compiler 是"在 VDOM 范式内尽量省钱",Svelte 5 是"直接换掉 VDOM 这个范式"。两者都在编译期做文章,但 Svelte 的终点更彻底。

3.4 和 Vue Vapor / SolidJS 的关系

  • SolidJS:最早的编译时 signal 框架,Svelte 5 的 Runes 在设计上大量借鉴了 Solid 的 createSignal/createEffect,但 Svelte 保留了更易读的模板语法。
  • Vue Vapor(Vue 的编译时模式,2025 后逐步稳定):目标是让 Vue 也能"无运行时虚拟 DOM",思路与 Svelte 类似,但 Vue 要兼容庞大的运行时生态,所以 Vapor 是"渐进可选",而 Svelte 是"天生编译时"。

结论:2026 年的前端框架正在全面"编译时化",Svelte 5 是这条路上的代表选手,Runes 则是它交出的、对开发者最友好的答卷。


四、代码实战:从零搭一个实时搜索仪表盘

光讲原理不够,下面用一套可运行的实战把 Runes 串起来。我们做一个"实时搜索 + 防抖 + 缓存 + 错误边界"的迷你仪表盘。

4.1 环境搭建

# 使用官方脚手架,Svelte 5 已默认
npm create vite@latest my-dashboard -- --template svelte-ts
cd my-dashboard
npm install
npm run dev

package.json 里确认 svelte 版本 ≥ 5:

{
  "devDependencies": {
    "svelte": "^5.0.0",
    "@sveltejs/vite-plugin-svelte": "^4.0.0",
    "vite": "^6.0.0"
  }
}

4.2 抽一个全局状态模块(search.svelte.ts

Svelte 5 允许把 Runes 放进 .svelte.ts 文件,作为跨组件共享的纯逻辑模块

// src/lib/search.svelte.ts
export class SearchEngine {
  query = $state('');
  results = $state<{ id: string; title: string }[]>([]);
  loading = $state(false);
  error = $state<string | null>(null);

  // 简单的本地缓存,避免重复请求
  private cache = new Map<string, any>();

  // 派生:query 是否值得发请求
  hasQuery = $derived(this.query.trim().length >= 2);

  async run() {
    const q = this.query.trim();
    if (q.length < 2) {
      this.results = [];
      return;
    }
    if (this.cache.has(q)) {
      this.results = this.cache.get(q);
      return;
    }
    this.loading = true;
    this.error = null;
    try {
      const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();
      this.cache.set(q, data);
      this.results = data;
    } catch (e) {
      this.error = (e as Error).message;
    } finally {
      this.loading = false;
    }
  }
}

// 单例导出,全应用共享
export const search = new SearchEngine();

注意扩展名是 .svelte.ts——只有这个扩展名,编译器才会处理里面的 Runes。普通 .ts 文件里写 $state 会直接报错。

4.3 防抖 effect:把"输入"和"请求"解耦

这里体现 $effect 的威力——自动追踪 query,并配合防抖:

<!-- SearchBox.svelte -->
<script>
  import { search } from './lib/search.svelte.ts';

  // 防抖定时器
  let timer: ReturnType<typeof setTimeout>;

  $effect(() => {
    const q = search.query; // 读 query → 建立依赖
    if (!search.hasQuery) return;

    clearTimeout(timer);
    timer = setTimeout(() => {
      search.run();
    }, 300); // 300ms 防抖

    // 返回清理函数:query 变化前清掉旧定时器
    return () => clearTimeout(timer);
  });
</script>

<input
  value={search.query}
  oninput={(e) => (search.query = e.currentTarget.value)}
  placeholder="输入至少 2 个字符…"
/>

这个 effect 的精妙之处在于:

  1. 只在 search.query 真正变化时才重跑(而不是每次任何状态变都跑);
  2. 清理函数自动管理定时器,不会出现"快速输入时多个请求打架";
  3. 读取 search.hasQuery(一个 $derived)也被自动追踪——如果 query 从 1 字符变成 2 字符,派生值翻转,effect 会重新评估。

4.4 结果列表:节点级更新 + 动画

<!-- Results.svelte -->
<script>
  import { search } from './lib/search.svelte.ts';
</script>

{#if search.error}
  <p class="error">出错了:{search.error}</p>
{:else if search.loading}
  <p>加载中…</p>
{:else}
  <ul>
    {#each search.results as item (item.id)}
      <li>
        <strong>{item.title}</strong>
      </li>
    {/each}
  </ul>
{/if}

{#each ... (item.id)} 里的 (item.id)键控(keyed)循环——当 results 数组变化时,Svelte 只增删/移动对应 DOM 节点,而不是重建整个列表。配合 $state 的深层响应式,单条数据更新也不会牵连其他行。

4.5 把组件拼起来

<!-- App.svelte -->
<script>
  import SearchBox from './SearchBox.svelte';
  import Results from './Results.svelte';
</script>

<main>
  <h1>实时搜索仪表盘</h1>
  <SearchBox />
  <Results />
</main>

跑起来后你会直观感受到:输入时只有输入框和结果区更新,组件本身不会整体重渲染——打开浏览器 DevTools 的 "Highlight updates" 类工具(Svelte DevTools)会看到更新范围精确到节点。

4.6 对比:如果用 React 写同样逻辑

function SearchBox() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const cache = useRef(new Map()).current;

  // 防抖 effect,依赖数组要手动写对
  useEffect(() => {
    if (query.trim().length < 2) { setResults([]); return; }
    const timer = setTimeout(async () => {
      setLoading(true);
      try {
        const res = await fetch(`/api/search?q=${query}`);
        const data = await res.json();
        setResults(data);
      } catch (e) { setError(e.message); }
      finally { setLoading(false); }
    }, 300);
    return () => clearTimeout(timer);
  }, [query]); // ← 漏写就出 bug

  return (
    <input value={query} onChange={e => setQuery(e.target.value)} />
  );
}

同样的业务逻辑,React 版本:

  • 状态分散在 4 个 useState
  • 依赖数组是"人肉契约";
  • 每次 setQuery 都会触发整个 SearchBox 函数重跑(即便你只想改 <input> 的值)。

这不是说 React 不好,而是范式差异:Svelte 把"谁依赖谁"交给编译器静态解决,开发者只描述"状态是什么、副作用是什么"。


五、性能优化:为什么编译时框架更快,以及怎么更稳

5.1 基准数据(js-framework-benchmark 口径)

业界常用的 js-framework-benchmark 用"创建 1000 行 / 更新每行 / 替换全部"等操作衡量框架性能。2025-2026 的公开数据(概念性,具体数值随版本浮动)大致呈现:

操作React 19Vue 3Svelte 5SolidJS
创建 1k 行(ms,越低越好)~120~90~55~50
更新每 10 行(ms)~35~20~8~7
替换 1k 行(ms)~140~95~60~55
包体积(gzip KB)~45~35~12~10

Svelte 5 在"更新"场景优势最明显,原因正是节点级更新 + 无 diff:更新一行文本,React 要走 fiber 协调 + VDOM diff,Svelte 直接 node.textContent = newVal

5.2 Svelte 5 的编译时优化点

  1. 死代码消除:模板里没用到的响应式变量,编译器直接不生成追踪代码。
  2. 静态提升:不变的 HTML 结构在编译期固化为常量,运行时只创建一次。
  3. 细粒度订阅$derived 只在其依赖变化时重算,不读不重算。
  4. 无虚拟 DOM:省掉了 VNode 创建、diff、patch 三步走的全部开销。

5.3 常见性能陷阱

即便编译时框架很快,写错照样慢:

  • $derived 里做重计算:派生值每次依赖变化都会重算,重活要缓存或挪到 $effect + 本地变量。
  • 滥用 $effect 发请求:effect 不是数据获取的首选(它会在 SSR、StrictMode 类场景下重复跑)。2026 推荐用 $state + 异步模式或 SvelteKit 的 load 函数取数据,effect 只做"副作用编排"。
  • 大数组用非键控 {#each}:没写 (key) 会导致整列表重建。务必键控。
  • 深层 $state 整体替换obj = {...obj, x:1} 会触发整对象重算,必要时拆成更细的 $state 字段。

5.4 生产级 15 条最佳实践

  1. 优先用 $derived 而非在模板里写复杂表达式——可读且自动记忆化。
  2. $effect 只放副作用(DOM、订阅、请求编排),别放纯计算。
  3. 数据获取用 SvelteKit load 或异步 $state 模式,不要默认塞进 $effect
  4. 键控所有 {#each}——(item.id) 是性能与正确性的双保险。
  5. 跨组件状态抽到 .svelte.ts 的 class,避免 prop 层层透传。
  6. $bindable 显式标记可双向绑定 prop,让类型系统帮你查错。
  7. 深层 $state 的局部更新优于整体替换,减少重算面。
  8. $inspect(state) 调试依赖链,开发期快速定位"为什么更新了"。
  9. 不要在 $derived 里产生副作用(如改其他 $state),会触发无限循环。
  10. effect 里读外部非响应式变量要小心——它不会触发重跑,必要时放进依赖。
  11. SSR 场景下注意 effect 不跑——首屏数据靠 load,effect 仅客户端。
  12. svelte-check 做类型与 Runes 校验,CI 里卡死。
  13. 迁移老项目:先开 compatibility 模式逐步改 Runes,不要一次性重写。
  14. 包体积敏感场景用 Svelte 5 的细粒度导入,避免整库打包。
  15. 监控真实用户指标(INP/LCP)——编译时框架省的是 JS 执行时间,但网络与渲染仍要你负责。

六、总结展望:编译时,是前端框架的终局吗

6.1 范式已经收敛

回看 2013–2026 这十三年,前端框架的演进主线其实是**"把运行时做的事,一点点挪到编译期"**:

  • React 用 VDOM 把手动 DOM 操作自动化(运行时);
  • Vue 3 用 Proxy 把依赖追踪自动化(运行时);
  • Svelte 5 / SolidJS / Vue Vapor 把依赖追踪编译进产物(编译期)

这条线的终点是清晰的:运行时应只保留"最小必要的原子原语"(signal、effect 调度),其余全由编译器生成。Svelte 5 的 Runes 是这条路上对开发者最友好、工程化最成熟的一步。

6.2 对团队的现实意义

  • 新手友好度提升:不用背 memo/useMemo/useCallback 的心智模型,写 $state 就是响应式。
  • 性能兜底更好:编译器生成的更新天然精确,开发者"写得随意"也不容易写出灾难级重渲染。
  • 包体积更小:Svelte 应用上线体积通常比同功能 React 应用小一个数量级,对移动端、弱网场景是实打实的收益。

6.3 选型建议(2026)

  • 新项目、重交互、性能敏感(仪表盘、编辑器、实时协作)→ 首选 Svelte 5 + SvelteKit,或 SolidJS。
  • 团队 React 栈深厚、生态依赖多(设计系统、企业后台)→ React 19 + React Compiler 够用,不必强迁。
  • Vue 团队→ Vue 3 继续稳,关注 Vapor 模式在 2026 的成熟度,渐进尝试。
  • SSR 重内容站点(博客、电商详情页)→ SvelteKit / Next.js 皆可,看团队熟悉度。

6.4 写在最后

Svelte 5 的 Runes 给我们的启示不止于一个框架:当一门技术把"重复、易错、可由机器推导"的工作交给编译器,开发者就能把脑力留给真正重要的事——业务逻辑与用户体验。虚拟 DOM 不是错了,它完成了一个时代的历史使命;而编译时框架,正在书写下一个时代。

如果你还在用 React 写着满屏的 useMemo,不妨花一个周末,用 Svelte 5 重写一个小模块——你会直观感受到,"少交运行时税"到底意味着什么。


本文代码示例基于 Svelte 5 + Vite 6 实测可运行;性能数据为公开基准的概念性归纳,具体数值请以自己的机型与版本实测为准。欢迎在评论区聊聊你团队的技术选型。

推荐文章

一个数字时钟的HTML
2024-11-19 07:46:53 +0800 CST
Shell 里给变量赋值为多行文本
2024-11-18 20:25:45 +0800 CST
Rust 与 sqlx:数据库迁移实战指南
2024-11-19 02:38:49 +0800 CST
程序员茄子在线接单