svelte笔记

发布于:2025-07-01 ⋅ 阅读:(27) ⋅ 点赞:(0)


‌Svelte‌是一个 编译型框架,以其独特的编译时优化机制著称,具有轻量级、高性能和易上手等特性。Svelte通过编译器将声明式组件转换为高效的JavaScript代码,直接更新DOM,从而提供高性能的Web应用。

特性

  1. 编译时优化‌:Svelte在编译阶段处理大部分逻辑,减少运行时的工作量,生成高度优化的原生JavaScript代码‌。
    • Svelte 的响应式系统基于观察者模式,但其独特之处在于它在构建阶段就识别出哪些变量是响应式的,并生成相应的更新代码。这意味着在运行时,Svelte 只需执行必要的 DOM 更新,无需额外的运行时框架代码来维护响应性。
    • Svelte 应用在开发时可能需要稍长的构建时间,但生产环境的性能很好。
  2. 轻量级‌:Svelte组件简洁且高效,适合构建轻量级Web项目‌。
  3. 高性能‌:由于编译时处理,Svelte应用在运行时表现非常流畅,适合构建复杂的交互式应用‌。
  4. 易上手‌:Svelte使用HTML、CSS和JavaScript,开发者可以快速上手并利用已有的技能‌。

编译过程

  • 代码提取:将框架相关的代码从组件中分离出来。
  • 代码优化:消除不必要的运行时检查和绑定。
  • 代码生成:生成优化后的 JavaScript 代码,只包含必要的运行时逻辑。

使用场景

Svelte特别适合构建以下类型的Web应用:

  • 个人项目‌:由于其轻量级和易上手的特性,Svelte是个人项目和小型项目的理想选择。
  • 交互式应用‌:由于编译时优化和高效执行,Svelte能够处理复杂的用户交互和数据更新。
  • 现代Web应用‌:结合SvelteKit,可以快速构建现代Web应用,并享受其高性能和易用性。

创建项目

npx sv create myapp
cd myapp
npm install
npm run dev
  • 应用的每个页面都是一个 Svelte 组件
  • 通过在项目的 src/routes 目录中添加文件来创建页面。这些页面将被服务端渲染,以确保用户首次访问您的应用时速度尽可能快,之后客户端应用将接管

问题1:build报错

上述操作启动项目,执行 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语句

{#if count < 3}
    <p>我是count小于3的时候显示的</p>
{:else if count >= 3 && count <= 6} 
    <p>我是count 大于等于 3 并且 小于等于 6的时候显示的</p>
{:else}
    <p>我是不符合的时候显示的</p>
{/if}

for循环

<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>
    <button on:click={() => addCat()}>添加猫猫</button>
    <ul>
        {#each cats as cat (cat.id)}
            <li>{cat.name}</li>
        {/each}
    </ul>
</div>

await加载数据

<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>

Event

<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>
    <button onclick={() => addSuccess('我是子组件,我传给父组件的消息')}>子组件点击</button>
</div>

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>
    <button onclick={() => addSuccess('我是子组件,我传给父组件的消息')}>子组件点击</button>
</div>

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

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>

slot插槽

child.svelte

<script lang="ts">

</script>
<div class="card">
    <slot name="header">default Header</slot>
    <slot>default content</slot>
    {#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

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>
onMount
import {onMount} from "svelte";

onMount(() => {
    console.log("mounted");
});
onDestroy
<script>
	import { onDestroy } from 'svelte';

	onDestroy(() => {
		console.log('onDestroy');
	});
</script>

SvelteKit 与 Svelte 的区别

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

+page.svelte

+page.svelte 组件定义了您应用程序的一个页面。默认情况下,页面在初始请求时在服务端渲染(SSR),在后续导航时在浏览器中渲染(CSR)。

+page.js/ts

通常,页面在渲染之前需要加载一些数据。为此,我们添加一个 +page.js 模块,该模块导出一个 load 函数:

这个函数与 +page.svelte 一起运行,这意味着它在服器端渲染期间在服务端上运行,在客户端导航期间在浏览器中运行。有关该 API 的完整详细信息,请参见 load

除了 load+page.js 还可以导出一些值用于配置页面行为:

  • export const prerender = truefalse'auto' // 预渲染

  • export const ssr = truefalse

    • 通常,SvelteKit 会先在服务端上渲染您的页面,并将该 HTML 发送到客户端,如果您将 ssr 设置为 false,它会改为渲染一个空的“外壳”页面。这在您的页面无法在服务端上渲染时(例如使用了只在浏览器可用的全局对象 document)会有用,但在大多数情况下并不推荐这样做(请参阅附录)。
    • 如果您在根 +layout.js 中添加 export const ssr = false,那么整个应用只会在客户端被渲染——这实际上意味着您将应用变成了一个 SPA。
  • export const csr = truefalse

    • 些页面根本不需要 JavaScript —— 很多博客文章或“关于”页面就是这种情况。对于这类页面,您可以禁用 CSR:

    • 禁用 CSR 不会向客户端发送任何 JavaScript。这意味着:

      • 网页只能通过 HTML 和 CSS 来工作。
      • 所有 Svelte 组件中的 <script> 标签将被移除。
      • <form> 元素无法进行渐进式增强
      • 链接由浏览器通过全页面导航来处理。
      • 将禁用热模块替换 (HMR)。

      您可以根据需要在开发环境中启用 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 中。

+error

如果在 load 期间发生错误,SvelteKit 将渲染默认错误页面。您可以通过添加 +error.svelte 文件来自定义每个路由的错误页面。

<script lang="ts">
  import { page } from '$app/state';
</script>

<h1>{page.status}: {page.error.message}</h1>

+layout

到目前为止,我们将页面视为完全独立的组件 —— 在导航时,现有的 +page.svelte 组件将被销毁,新的组件将取而代之。

但在许多应用中,有些元素应该在每个页面上都可见,比如顶层导航或页脚。与其在每个 +page.svelte 中重复它们,我们可以将它们放在布局中。

+layout.svelte

要创建一个适用于每个页面的布局,创建一个名为 src/routes/+layout.svelte 的文件。默认布局(即当你没有提供自己的布局时 SvelteKit 使用的布局)看起来是这样的…

<script>
  let { children } = $props();
</script>

{@render children()}

+layout.svelte 文件也可以通过 +layout.js+layout.server.js 加载数据。

layout.server.ts

获取布局数据

import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async () => {
	return {
		posts: await db.getPostSummaries()
	};
};

load方法的参数

  • url

    URL 的一个实例,包含诸如 originhostnamepathnamesearchParams(包含解析后的查询字符串,作为 URLSearchParams 对象)等属性。在 load 期间无法访问 url.hash,因为它在服务端上不可用。

  • route

    包含当前路由目录相对于 src/routes 的名称:

  • params

params 是从 url.pathnameroute.id 派生的。

发起 fetch 请求

要从外部 API 或 +server.js 处理程序获取数据,您可以使用提供的 fetch 函数,它的行为与原生 fetch web API完全相同,但有一些额外的功能:

  • 它可以在服务端上发起带凭据的请求,因为它继承了页面请求的 cookieauthorization 标头。
  • 它可以在服务端上发起相对请求(通常,当在服务端上下文中使用时,fetch 需要带有源的 URL)。
  • 内部请求(例如对 +server.js 路由的请求)在服务端上运行时直接转到处理函数,无需 HTTP 调用的开销。
  • 在服务端渲染期间,通过钩入 textjsonarrayBuffer 方法来捕获响应并将其内联到渲染的 HTML 中 Response 对象。请注意,除非通过 filterSerializedResponseHeaders 显式包含,否则标头将不会被序列化。
  • 在水合过程中,响应将从 HTML 中读取,确保一致性并防止额外的网络请求 - 如果在使用浏览器 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 调用的开销。
  • 在服务端渲染期间,通过钩入 textjsonarrayBuffer 方法来捕获响应并将其内联到渲染的 HTML 中 Response 对象。请注意,除非通过 filterSerializedResponseHeaders 显式包含,否则标头将不会被序列化。
  • 在水合过程中,响应将从 HTML 中读取,确保一致性并防止额外的网络请求 - 如果在使用浏览器 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 };
};

网站公告

今日签到

点亮在社区的每一天
去签到