- name
- svelte-misc
- license
- Apache-2.0
- description
- Svelte 5 杂项技能。当用户需要了解 TypeScript 支持、自定义元素、浏览器兼容性、Svelte 4→5 迁移、FAQ 时使用。
# Svelte Misc Reference (Svelte 5)
本技能覆盖 Svelte 5 的 TypeScript 高级支持、自定义元素(Web Components)、浏览器兼容性矩阵、Svelte 4→5 迁移指南、官方最佳实践与常见问答。
## When to use this skill
当用户需要 TypeScript 类型标注(泛型、包装组件、DOM 类型增强)、创建自定义 Web 组件并使用 `$host()` / `$bindable` / `extend`、查看浏览器兼容性矩阵与功能例外、从 Svelte 4 迁移到 Svelte 5,或遵循 Svelte 5 推荐的 `$state` / `$derived` / `$effect` 模式时使用本技能。
---
## Critical: TypeScript 高级
Svelte 原生支持 TypeScript(Vite 项目无需配置)。用 `<script lang="ts">` 启用类型注解;需要枚举 / 参数属性等运行时特性则需要预处理器。
```svelte
<script lang="ts">
let name: string = 'world';
function greet(name: string) { alert(`Hello, ${name}!`); }
</script>
<button onclick={(e: Event) => greet((e.target as HTMLElement).innerText)}>
{name}
</button>
```
> `lang="ts"` 默认只支持「类型只消失」的特性(注解、接口、泛型)。运行时代码特性(enum、`private foo = 1` 形式的参数属性)需配置 `vitePreprocess({ script: true })`。
### 类型 Props + 透传
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
requiredProperty: number;
optionalProperty?: boolean;
snippetWithStringArgument: Snippet<[string]>;
eventHandler: (arg: string) => void;
[key: string]: unknown; // 配合 ...rest 透传其他属性
}
let { requiredProperty, optionalProperty, snippetWithStringArgument,
eventHandler, ...rest }: Props = $props();
</script>
```
### 泛型组件
`<script>` 的 `generics` 属性接受与函数泛型同样的语法:
```svelte
<script lang="ts" generics="Item extends { text: string }">
interface Props { items: Item[]; select(item: Item): void; }
let { items, select }: Props = $props();
</script>
{#each items as item}
<button onclick={() => select(item)}>{item.text}</button>
{/each}
```
### 包装组件(转发原生属性)
```svelte
<script lang="ts">
import type { HTMLButtonAttributes } from 'svelte/elements';
let { children, ...rest }: HTMLButtonAttributes = $props();
</script>
<button {...rest}>{@render children?.()}</button>
```
无专用接口的元素用 `SvelteHTMLElements['div']` 形式。`Component` / `ComponentProps<T>` 用于约束动态组件与提取现有组件的 props。
### 类中 `$state`
```ts
class Counter {
count = $state() as number; // 无初值类型会扩为 undefined
constructor(initial: number) { this.count = initial; }
increment() { this.count += 1; }
}
```
### 增强 `svelte/elements`
```ts
/// file: additional-svelte-typings.d.ts
declare module 'svelte/elements' {
export interface SvelteHTMLElements { 'custom-button': HTMLButtonAttributes; }
export interface HTMLAttributes<T> { globalattribute?: string; }
}
export {}; // 必须——否则会覆盖原模块
```
---
## Critical: Custom Elements(Web Components)
### 基础用法
```svelte
<svelte:options customElement="my-element" />
<script>
let { name = 'world' } = $props();
</script>
<h1>Hello {name}!</h1>
<slot />
```
导入即自动 `customElements.define()` 注册。消费者可读写 DOM 属性:
```js
const el = document.querySelector('my-element');
el.name = 'everybody'; // 触发 shadow DOM 更新
```
> 必须显式声明 props(不能只写 `let props = $props()`),否则 Svelte 不知道要暴露哪些键。
### `customElement` 选项
```svelte
<svelte:options customElement={{
tag: 'my-counter',
shadow: 'open',
props: { count: { type: 'Number', reflect: true, attribute: 'start' } },
extend: (Ctor) => class extends Ctor { /* ... */ }
}} />
```
| 字段 | 取值 | 默认 | 作用 |
|------|------|------|------|
| `tag` | string | — | 标签名;设置后自动注册 |
| `shadow` | `'none' \| 'open' \| 'closed' \| ShadowRootInit` | `'open'` | Shadow root 配置 |
| `props.<name>.attribute` | string | 小写 prop 名 | 自定义属性名 |
| `props.<name>.reflect` | boolean | `false` | 将 prop 值镜像到 HTML 属性 |
| `props.<name>.type` | `'String' \| 'Boolean' \| 'Number' \| 'Array' \| 'Object'` | `'String'` | 属性 ↔ prop 类型转换 |
| `extend` | `(Ctor) => class` | — | 包装生成的类(可注入 lifecycle / ElementInternals)|
### `$host()` Rune + 生命周期
```svelte
<svelte:options customElement="tooltip" />
<script>
import { $host } from 'svelte';
let { text = 'hi' } = $props();
function focus() { $host().focus(); }
</script>
<button onclick={focus}>{text}</button>
```
- 内部组件在 `connectedCallback` 之后的**下一个 tick** 创建;插入前设置的属性被缓冲并应用。
- Shadow DOM 更新批处理到下一个 tick;DOM 移动不会触发不必要的卸载。
- `disconnectedCallback` 后下一个 tick 销毁。组件方法 mount 后才可用;要更早暴露请在 `extend` 类里声明。
### 重要注意事项
- 样式**封装**而非「作用域」——全局 CSS 无法穿透 shadow root。
- CSS 内联为 JS 字符串而非单独 `.css` 文件。
- **不**适合 SSR(shadow DOM 在 JS 加载前不可见)。
- 插槽内容**积极渲染**(不像 Svelte 默认懒渲染),`<slot>` 在 `{#each}` 里不会复制内容。
- `let:` 指令对自定义元素**无效**。Svelte `context` 不能跨自定义元素边界。
- 不要命名 `on*` 属性(被解释为事件监听器)。
---
## Critical: Browser Support
Svelte 5 目标是 **Baseline 2020**。
| 浏览器 | 最低版本 |
|--------|----------|
| Chrome / Edge | 87 |
| Firefox | 83 |
| Safari | 14 |
| Opera | 73 |
| Opera (Android) | 62 |
| Samsung Internet | 14.0 |
| Android WebView | 87 |
| Internet Explorer | **不支持** |
### 例外情况
| 功能 | Chrome/Edge | Firefox | Safari |
|------|-------------|---------|--------|
| `$state.snapshot` | 98 | 94 | 15.4 |
| `bind:devicePixelContentBoxSize` | — | 93 | 不支持 |
| `flip`(来自 `svelte/animate`) | — | 126 | — |
### SSR 浏览器 API 保护
```svelte
<script>
import { onMount } from 'svelte';
onMount(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
});
</script>
```
> 优先用 `<svelte:window>` / `<svelte:document>`;Svelte 自动处理 SSR 与卸载。
---
## Critical: Best Practices
### `$state` — 只在需要时声明
只把会触发更新(`$effect` / `$derived` / 模板)的值声明为 `$state`。大型只重赋值对象(API 响应)用 `$state.raw` 避免 Proxy 开销:
```js
let user = $state.raw<User | null>(null);
```
### `$derived` > `$effect` 用于纯计算
```js
let square = $derived(num * num); // 好
// let square; $effect(() => { square = num * num; }); // 差
```
复杂逻辑用 `$derived.by(() => { ... })`。Derived 可写;对象/数组结果不会被深层代理。
### `$effect` 是 escape hatch
优先工具:同步外部库 → `{@attach ...}`;响应用户输入 → 处理器;调试日志 → `$inspect` / `$inspect.trace`;观察外部源 → `createSubscriber`。永远不要 `if (browser) { ... }` 包 effect——effect 本就不在服务端运行。
### Props 当作会变化、事件用属性语法
```js
let { type } = $props();
let color = $derived(type === 'danger' ? 'red' : 'green');
```
`<svelte:window>` / `<svelte:document>` 用于全局监听;不要 `onMount` 手动绑。Snippets 替代 Slots,`{#each ... (item.id)}` 用稳定 key(不可用 index)。`style:--` 把 JS 变量透到 CSS 自定义属性;用 CSS 变量(而非 `:global`)穿透子组件样式;用 `createContext` 替代模块级共享状态(提供类型安全 + 防止 SSR 用户间泄漏)。
### 新代码避免 Legacy
`on:click` → `onclick`;`export let` → `$props()`;`$:` → `$derived` / `$effect`;`<slot>` → snippet;`use:action` → `{@attach ...}`。
---
## Critical: Svelte 4 Migration Guide
### 最小版本
| 依赖 | 要求 |
|------|------|
| Node | 16+ |
| SvelteKit | 1.20.4+(如使用) |
| `vite-plugin-svelte` | 2.4.1+ |
| TypeScript | 5+ |
### 核心迁移对照
| Svelte 4 | Svelte 5 |
|---------|---------|
| `let x = 0`(顶层响应式) | `let x = $state(0)` |
| `$: x = a + b` | `let x = $derived(a + b)` |
| `$: { ... }` | `$effect(() => { ... })` |
| `export let prop` | `let { prop } = $props()` |
| `<slot />` | `{@render children()}` |
| `createEventDispatcher` | callback props |
| `on:click={fn}` | `onclick={fn}` |
### 自动迁移工具
```bash
npx svelte-migrate@latest svelte-5 my-project
```
### 重要行为变化
- 转换默认 `local`(外层控制流不影响子 transition)。
- 默认插槽绑定**不会**进入具名插槽。
- 预处理器顺序:markup → script → style(按声明顺序逐个执行)。
- `createEventDispatcher` 严格模式下检查可选 / 必传 / 无参类型(之后用 callback props 替代)。
- `Action` / `ActionReturn` 默认参数类型为 `undefined`,需要参数时显式声明。
- `SvelteComponentTyped` 已弃用,统一用 `SvelteComponent`。
- CJS 输出与 `svelte/register` 被移除。
- 删除 `svelte.JSX`,改用 `svelteHTML` / `svelte/elements`。
- 打包器必须指定 `browser` 条件(SvelteKit / Vite 自动;Rollup 用 `@rollup/plugin-node-resolve` 设 `browser: true`;Webpack 在 `conditionNames` 加 `"browser"`)。
---
## Critical: Svelte 5 Migration Guide (Concept Summary)
Runes 系统:`$state` / `$derived` / `$effect` 替代隐式响应式;Snippets 替代 Slots;事件属性替代 `on:`;`$effect` 显式追踪依赖;`$state` 对对象/数组做深层 Proxy。
| 移除 | 替代 |
|------|------|
| `let` 顶层隐式响应式 | `$state` |
| `$:` 语句 | `$derived` / `$effect` |
| `export let` / `$$props` / `$$restProps` | `$props()` |
| `createEventDispatcher` | callback props / `$bindable` |
| Slots | Snippets |
| `on:event` | `onevent` |
| `beforeUpdate` / `afterUpdate` | `$effect.pre` / `$effect` |
新增:`$state.eager`、`$effect.pre`、`$effect.tracking`、`$state.snapshot`、`hydratable`、`fork()`、`{#key}` 过渡触发、`<svelte:boundary>`、`createContext`。
---
## FAQ
**Q: Svelte 和 Vue/React 的核心区别?**
A: Svelte 无虚拟 DOM——编译时生成精确 DOM 更新代码,运行时零框架开销。
**Q: Svelte 5 和 Svelte 4 可以共存吗?**
A: 可以。Svelte 5 仍支持 Legacy Mode,Svelte 4 的 `$:` 与 `export let` 可继续用;可在同一项目渐进迁移。
**Q: 如何升级到 Svelte 5?**
Ver en GitHub