Svelte特别适合构建以下类型的Web应用:
SvelteKit
,可以快速构建现代Web应用,并享受其高性能和易用性。npx sv create myapp
cd myapp
npm install
npm run dev
src/routes
目录中添加文件来创建页面。这些页面将被服务端渲染,以确保用户首次访问您的应用时速度尽可能快,之后客户端应用将接管上述操作启动项目,执行 npm run build
时报错,配置fallback
https://svelte.dev/docs/kit/single-page-apps#Usage
// svelte.config.js
kit: {
adapter: adapter({
fallback: '200.html'
}),
}
计算属性、watch监听
<script lang="ts">
let count = 0;
// 计算属性
$: doubleCount = count * 2;
// watch监听
$: if (count) {
console.log('✨file:+page.svelte:11✨✨ ', count);
}
function handleAdd() {
count++;
}
function handleSub() {
count--;
}
script>
<button on:click={handleSub}>减button>
<span>{count}span>
<button on:click={handleAdd}>加button>
<span>{doubleCount}span>
{#if count < 3}
<p>我是count小于3的时候显示的p>
{:else if count >= 3 && count <= 6}
<p>我是count 大于等于 3 并且 小于等于 6的时候显示的p>
{:else}
<p>我是不符合的时候显示的p>
{/if}
<script lang="ts">
// for
let cats = [
{id: '1', name: 'Cat1'},
{id: '2', name: 'Cat2'},
{id: '3', name: 'Cat3'},
]
const addCat = () => {
cats.push({id: cats.length +1+'', name: 'Cat' + (cats.length +1)});
console.log('✨file:+page.svelte:22✨addCat✨ ', cats);
cats = [...cats]
}
script>
<div>
<script lang="ts">
let cats = [
{id: '1', name: 'Cat1'},
{id: '2', name: 'Cat2'},
{id: '3', name: 'Cat3'},
]
// 网络请求
async function sleep() {
return new Promise(resolve => {
setTimeout(() => {
resolve(cats);
}, 2e3)
})
}
let request: Promise<any> = loadData();
function handleRequest() {
request = loadData();
}
async function loadData() {
return await sleep();
}
script>
<div>
<button on:click={handleRequest}>加载数据button>
{#await request}
<p>loading...p>
{:then list}
<ul>
{#each list as cat (cat.id)}
<li>{cat.name}li>
{/each}
ul>
{/await}
div>
<script lang="ts">
let m = {x:0, y:0}
function handleMove(event) {
m.x = event.clientX;
m.y = event.clientY;
}
const handleMoveClick = () => {
console.log('✨file:+page.svelte:56✨handleMoveClick✨ ');
}
script>
<div class="box" on:mousemove={handleMove} role="application">
the mouse position is {m.x} X {m.y}
div>
<div>
<button on:click|once={handleMoveClick}>只可点击一次button>
div>
<style>
.box {
width: 500px;
height: 200px;
border: 1px solid black;
}
style>
child.svelte
<script lang="ts">
let {addSuccess, msg} = $props();
script>
<div>
<h1>Child {msg}h1>
parent.svelte
<script lang="ts">
import Child from "./Child.svelte";
// 子组件
const handleSubDeflate = (e: any) => {
console.log('✨父组件:子组件点击回调事件✨ ', e);
}
script>
<Child msg="hello world" addSuccess={handleSubDeflate}/>
child.svelte
<script lang="ts">
import {getContext} from "svelte";
let {addSuccess, msg} = $props();
const pubMsg = getContext('pubMsg');
script>
<div>
<h1>Child {msg}h1>
<p>上下文数据:{pubMsg}p>
parent.svelte
<script lang="ts">
import Child from "./Child.svelte";
import {setContext} from "svelte";
// 子组件
const handleSubDeflate = (e: any) => {
console.log('✨父组件:子组件点击回调事件✨ ', e);
}
// 设置数据
setContext('pubMsg', '我是公共数据')
script>
<Child msg="hello world" addSuccess={handleSubDeflate}/>
store/userStore.ts
import {type Writable, writable} from 'svelte/store'
interface User {
id: string
userName: string
}
export const userStore: Writable<User | null> = writable(null)
页面使用store
<script lang="ts">
import {userStore} from "../../store/userStore";
const handleSetInfo = () => {
userStore.set({
userName: 'zs',
id: '1'
})
}
const updateUser = () => {
userStore.update((user: any) => {
user.userName = 'abc'
return user;
})
}
script>
<h1>个人资料h1>
{#if $userStore}
<p>{$userStore.userName} --- {$userStore.id}p>
{/if}
<button on:click={handleSetInfo}>设置用户信息button>
<button on:click={updateUser}>更新用户信息button>
child.svelte
<script lang="ts">
script>
<div class="card">
<slot name="header">default Headerslot>
<slot>default contentslot>
{#if $$slots.footer}
<slot name="footer">slot>
{/if}
div>
<style>
.card {
border: 1px solid #eeeeee;
}
style>
使用组件
<script lang="ts">
import Child from "./Child.svelte";
script>
<Child>
<h1 slot="header">头部h1>
<p>组件内容<br>组件内容<br>组件内容<br>组件内容p>
<h2 slot="footer">底部h2>
Child>
tick()
函数是一个重要的生命周期函数,用于处理异步操作和DOM更新。在Svelte中,tick()
函数确保所有的DOM更新都已完成,这对于处理依赖于DOM更新的逻辑非常有用。
<script lang="ts">
import {tick} from "svelte";
let text = 'Select some text and hit the tab key to toggle uppercase'
const handleKeydown = (event: any) => {
if (event.key !== 'Tab') return;
if (event.target) {
const {selectionStart, selectionEnd, value} = event.target;
const selection = value.slice(selectionStart, selectionEnd);
const replacement = /[a-z]/.test(selection)? selection.toUpperCase() : selection.toLowerCase();
text = value.slice(0, selectionStart) + replacement + value.slice(selectionEnd);
tick().then(() => {
event.target.selectionStart = selectionStart;
event.target.selectionEnd = selectionEnd;
})
}
}
script>
<textarea bind:value={text} on:keydown|preventDefault={handleKeydown}>textarea>
<style>
textarea {
width: 100%;
height: 200px;
}
style>
import {onMount} from "svelte";
onMount(() => {
console.log("mounted");
});
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
console.log('onDestroy');
});
script>
SvelteKit 是一个使用 Svelte 快速开发稳健、高性能 Web 应用程序的框架。如果您来自 React,SvelteKit 类似于 Next。如果您来自 Vue,SvelteKit 类似于 Nuxt。
Svelte 负责渲染 UI 组件。您可以组合这些组件并仅使用 Svelte 渲染整个页面,但要构建完整的应用程序,您需要的不仅仅是 Svelte。
SvelteKit 帮助您在遵循现代最佳实践的同时构建 Web 应用,并为常见的开发挑战提供解决方案。它提供从基本功能 —— 比如在点击链接时更新 UI 的路由 —— 到更高级的功能。
它的广泛功能列表包括:仅加载最小所需代码的构建优化;离线支持;用户导航前的页面预加载;通过 SSR、浏览器客户端渲染或构建时预渲染来处理应用程序不同部分的可配置渲染;图像优化;等等。使用所有现代最佳实践构建应用程序非常复杂,但 SvelteKit 为您处理了所有繁琐的工作,这样您就可以专注于创造性的部分。
它利用带有 Svelte 插件的 Vite 来实现热模块替换 (HMR),从而在浏览器中即时反映代码更改,提供闪电般快速且功能丰富的开发体验。
my-project/
├ src/
│ ├ lib/
│ │ ├ server/
│ │ │ └ [您的仅服务端库文件]
│ │ └ [您的库文件]
│ ├ params/
│ │ └ [您的参数匹配器]
│ ├ routes/
│ │ └ [您的路由]
│ ├ app.html
│ ├ error.html
│ ├ hooks.client.js
│ ├ hooks.server.js
│ └ service-worker.js
├ static/
│ └ [您的静态资源]
├ tests/
│ └ [您的测试]
├ package.json
├ svelte.config.js
├ tsconfig.json
└ vite.config.js
SvelteKit 的核心是一个基于文件系统的路由器。
src/routes
是根路由src/routes/about
创建一个 /about
路由src/routes/blog/[slug]
创建一个带有参数 slug
的路由,当用户请求类似 /blog/hello-world
的页面时,可以用它动态加载数据每个路由目录包含一个或多个路由文件,这些文件可以通过它们的 +
前缀识别。
简单的规则:
+server
文件外,所有文件都在客户端运行+layout
和 +error
文件不仅适用于它们所在的目录,也适用于子目录+page.svelte
组件定义了您应用程序的一个页面。默认情况下,页面在初始请求时在服务端渲染(SSR),在后续导航时在浏览器中渲染(CSR)。
通常,页面在渲染之前需要加载一些数据。为此,我们添加一个 +page.js
模块,该模块导出一个 load
函数:
这个函数与 +page.svelte
一起运行,这意味着它在服器端渲染期间在服务端上运行,在客户端导航期间在浏览器中运行。有关该 API 的完整详细信息,请参见 load
。
除了 load
,+page.js
还可以导出一些值用于配置页面行为:
export const prerender = true
或 false
或 'auto'
// 预渲染
export const ssr = true
或 false
ssr
设置为 false
,它会改为渲染一个空的“外壳”页面。这在您的页面无法在服务端上渲染时(例如使用了只在浏览器可用的全局对象 document
)会有用,但在大多数情况下并不推荐这样做(请参阅附录)。+layout.js
中添加 export const ssr = false
,那么整个应用只会在客户端被渲染——这实际上意味着您将应用变成了一个 SPA。export const csr = true
或 false
些页面根本不需要 JavaScript —— 很多博客文章或“关于”页面就是这种情况。对于这类页面,您可以禁用 CSR:
禁用 CSR 不会向客户端发送任何 JavaScript。这意味着:
标签将被移除。
元素无法进行渐进式增强。您可以根据需要在开发环境中启用 csr
(例如为了使用 HMR):
您可以在页面选项中找到更多相关信息。
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
return {
post: {
title: `Title for ${params.slug} goes here`,
content: `Content for ${params.slug} goes here`
}
};
};
上面的load的数据,可以在+page.svelte中获取,方式如下:
<script lang="ts">
import type { PageData } from './$types';
// 在 Svelte 4 中,您需要使用 export let data 代替
let { data }: { data: PageData } = $props();
script>
<h1>{data.post.title}h1>
<div>{@html data.post.content}div>
+page.js
文件中的 load
函数在服务端和浏览器上都会运行(除非与 export const ssr = false
结合使用,在这种情况下它将仅在浏览器中运行)。如果您的 load
函数应该始终在服务端上运行(例如,因为它使用了私有环境变量或访问数据库),那么它应该放在 +page.server.js
中。
如果在 load
期间发生错误,SvelteKit 将渲染默认错误页面。您可以通过添加 +error.svelte
文件来自定义每个路由的错误页面。
<script lang="ts">
import { page } from '$app/state';
script>
<h1>{page.status}: {page.error.message}h1>
到目前为止,我们将页面视为完全独立的组件 —— 在导航时,现有的 +page.svelte
组件将被销毁,新的组件将取而代之。
但在许多应用中,有些元素应该在每个页面上都可见,比如顶层导航或页脚。与其在每个 +page.svelte
中重复它们,我们可以将它们放在布局中。
要创建一个适用于每个页面的布局,创建一个名为 src/routes/+layout.svelte
的文件。默认布局(即当你没有提供自己的布局时 SvelteKit 使用的布局)看起来是这样的…
<script>
let { children } = $props();
script>
{@render children()}
+layout.svelte
文件也可以通过 +layout.js
或 +layout.server.js
加载数据。
获取布局数据
import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => {
return {
posts: await db.getPostSummaries()
};
};
url
URL
的一个实例,包含诸如 origin
、hostname
、pathname
和 searchParams
(包含解析后的查询字符串,作为 URLSearchParams
对象)等属性。在 load
期间无法访问 url.hash
,因为它在服务端上不可用。
route
包含当前路由目录相对于 src/routes
的名称:
params
params
是从 url.pathname
和 route.id
派生的。
要从外部 API 或 +server.js
处理程序获取数据,您可以使用提供的 fetch
函数,它的行为与原生 fetch
web API完全相同,但有一些额外的功能:
cookie
和 authorization
标头。fetch
需要带有源的 URL)。+server.js
路由的请求)在服务端上运行时直接转到处理函数,无需 HTTP 调用的开销。text
、json
和 arrayBuffer
方法来捕获响应并将其内联到渲染的 HTML 中 Response 对象。请注意,除非通过 filterSerializedResponseHeaders
显式包含,否则标头将不会被序列化。fetch
而不是 loadfetch
时,在浏览器控制台中收到警告,这就是原因。import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, params }) => {
const res = await fetch(`/api/items/${params.id}`);
const item = await res.json();
return { item };
};
要带有源的 URL)。
+server.js
路由的请求)在服务端上运行时直接转到处理函数,无需 HTTP 调用的开销。text
、json
和 arrayBuffer
方法来捕获响应并将其内联到渲染的 HTML 中 Response 对象。请注意,除非通过 filterSerializedResponseHeaders
显式包含,否则标头将不会被序列化。fetch
而不是 loadfetch
时,在浏览器控制台中收到警告,这就是原因。import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, params }) => {
const res = await fetch(`/api/items/${params.id}`);
const item = await res.json();
return { item };
};