1. 概述

  • Svelte 是一个前端框架,专注于编译时优化,区别于 React 和 Vue,它通过在编译时将组件转化为高效的 JavaScript 代码,消除了虚拟 DOM 的性能开销。
  • 响应性:数据变化会直接影响视图,Svelte 利用编译时的静态分析自动处理组件的更新和重新渲染。
  • 无框架运行时:Svelte 生成的代码没有框架运行时,这使得生成的应用体积小、性能高。

2. 基础概念

  • Svelte 文件:每个组件是一个 .svelte 文件,包含三个部分:HTML、JavaScript、CSS。
    • HTML:组件的结构和模板。
    • JavaScript:组件的逻辑和数据。
    • CSS:样式表,支持局部样式。
  • 组件:Svelte 的应用由多个组件组成,每个组件是独立的封装单元。组件间可以通过 props、events 和 store 进行交互。
  • 编译过程:在开发时,Svelte 会将 .svelte 文件中的代码转化为高效的 JavaScript,减少运行时依赖,减少浏览器负担。

3. 响应式设计

  • 响应式声明(Reactivity):Svelte 使用编译时的响应式语法,不需要手动管理依赖关系。通过 $: 来声明响应式变量,任何依赖该变量的地方会在其变化时自动更新。

    1
    2
    let count = 0;
    $: doubled = count * 2; // doubled 会自动更新
  • 响应式语法:你可以直接在模板中使用变量和计算值,它们会在相关数据变化时自动更新视图。

    1
    <p>{doubled}</p>  // 当 count 改变时,doubled 会自动更新
  • 赋值响应式:直接将某个值赋给另一个变量,也会触发响应式更新。

    1
    2
    let name = "Alice";
    $: greeting = `Hello, ${name}!`;

4. 组件之间的数据传递

  • Props:通过 export 导出变量,使其成为组件的输入属性。子组件可以通过传递 props 从父组件接收数据。

    1
    2
    3
    4
    5
    <script>
    export let name;
    </script>

    <p>Hello, {name}!</p>

    父组件:

    1
    <Child name="Alice" />
  • 事件:子组件可以通过 $emit 触发事件,父组件可以监听并响应这些事件。

    1
    <button on:click={handleClick}>Click Me</button>
  • 双向数据绑定:使用 bind: 语法来实现双向绑定,允许父组件和子组件之间同步数据。

    1
    <input bind:value={name}>
  • Store:Svelte 提供了 store 来管理全局状态,writable 用于创建可变的状态,readable 用于只读状态。

    1
    2
    import { writable } from 'svelte/store';
    const count = writable(0);

5. 条件渲染和列表渲染

  • 条件渲染:使用 {#if} 语法来进行条件渲染。

    1
    2
    3
    {#if count > 0}
    <p>Count is greater than 0</p>
    {/if}
  • **条件渲染的 elseelseif**:可以配合 elseelseif 来处理多个条件分支。

    1
    2
    3
    4
    5
    6
    7
    {#if count > 0}
    <p>Count is positive</p>
    {:else if count < 0}
    <p>Count is negative</p>
    {:else}
    <p>Count is zero</p>
    {/if}
  • 列表渲染:使用 {#each} 来遍历数组并渲染。

    1
    2
    3
    {#each items as item}
    <p>{item}</p>
    {/each}
  • 列表渲染中的键值(Keyed each blocks):通过 key 为每个项目分配唯一标识符,以优化渲染性能。

    1
    2
    3
    {#each items as item (item.id)}
    <p>{item.name}</p>
    {/each}

6. 生命周期钩子

  • **onMount**:组件挂载时调用,类似于 componentDidMount(React)或 mounted(Vue)。

    1
    2
    3
    4
    import { onMount } from 'svelte';
    onMount(() => {
    console.log('Component mounted');
    });
  • **beforeUpdateafterUpdate**:在组件更新前后调用,用于执行一些副作用。

    1
    2
    3
    import { beforeUpdate, afterUpdate } from 'svelte';
    beforeUpdate(() => { console.log('Before update'); });
    afterUpdate(() => { console.log('After update'); });
  • **onDestroy**:在组件销毁时调用,类似于 componentWillUnmount(React)。

    1
    2
    3
    4
    import { onDestroy } from 'svelte';
    onDestroy(() => {
    console.log('Component destroyed');
    });

7. 样式和样式作用域

  • 局部样式:Svelte 中的样式默认是局部的,仅作用于当前组件内的元素。

    1
    2
    3
    <style>
    p { color: red; }
    </style>
  • 全局样式:如果需要全局样式,可以使用 :global 关键字。

    1
    2
    3
    4
    5
    <style>
    :global(body) {
    font-family: Arial, sans-serif;
    }
    </style>
  • CSS 类绑定:使用 {className} 来动态添加类名。

    1
    <button class:active={isActive}>Click Me</button>

8. 动画与过渡

  • 过渡动画:Svelte 提供了内置的过渡效果,如 fadeflyslide 等,可以直接应用在元素上。

    1
    2
    3
    <div transition:fade>
    This will fade in/out
    </div>
  • 自定义过渡:可以定义自己的过渡效果,利用 transition 导入不同的过渡动画函数。

    1
    2
    import { fade, slide } from 'svelte/transition';
    <div transition:fade>Content</div>
  • 动画:Svelte 也提供了动画支持,可以基于元素的状态或属性变化进行动画处理。

    1
    2
    import { fly } from 'svelte/transition';
    <div transition:fly={{ x: 200 }}>Slide in</div>

9. 路由

  • 没有内建路由功能:Svelte 不自带路由功能,但有第三方库如 svelte-routingsvelte-navigator 来支持路由。

    1
    pnpm add svelte-routing
  • **使用 svelte-routing**:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script>
    import { Router, Route, Link } from 'svelte-routing';
    </script>

    <Router>
    <nav>
    <Link to="/">Home</Link>
    <Link to="/about">About</Link>
    </nav>
    <Route path="/" component={Home} />
    <Route path="/about" component={About} />
    </Router>

10. SvelteKit(用于 SSR 和 SSG)

  • SvelteKit 是 Svelte 官方推出的框架,提供了更高级的功能,包括服务器端渲染(SSR)、静态站点生成(SSG)和文件路由。

  • 页面路由:SvelteKit 使用文件系统自动生成路由。每个 .svelte文件都会变成一个页面。

    1
    2
    3
    src/routes/
    └── index.svelte // 默认首页
    └── about.svelte // About 页面

11. 工具和生态

  • Svelte DevTools:浏览器扩展工具,帮助开发者调试 Svelte 应用。
  • Svelte Preprocess:支持 TypeScript、SCSS、Less 等预处理器。
  • 社区插件:Svelte 拥有很多社区插件,能够扩展其功能,如表单处理、路由、状态管理等。

12. 优缺点

  • 优点:
    • 编译时优化,生成高效的原生 JavaScript。
    • 响应式设计简洁易懂,自动追踪数据变化。
    • 生成的代码体积小,运行时无框架,性能极高。
  • 缺点:
    • 社区和生态系统较小,第三方库支持不如 React 和 Vue。
    • 对大型项目的支持还不够成熟,缺乏一些成熟的开发工具。

总结

Svelte 是一个具有创新性的框架,尤其适合需要高性能和小体积的前端应用。它的响应式编程模式和无虚拟 DOM 的设计理念让开发者能够专注于业务逻辑,而不需要考虑性能优化。尽管生态系统不如 React 和 Vue 丰富,但 Svelte 的简洁性和高效性已吸引了越来越多的开发者。

基础项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
my-svelte-app/
├── public/ # 公共资源文件夹
│ ├── global.css # 全局样式
│ ├── favicon.ico # 网站图标
│ ├── index.html # HTML 模板
│ └── ...
├── src/ # 源代码
│ ├── assets/ # 静态资源(如图片、字体等)
│ ├── components/ # 组件
│ ├── routes/ # 路由(SvelteKit 中的页面文件)
│ ├── stores/ # 状态管理(store)
│ ├── app.css # 应用的 CSS
│ ├── app.js # 应用的入口文件
│ └── main.js # 应用入口
├── svelte.config.js # Svelte 配置文件
├── package.json # 项目配置和依赖管理
└── rollup.config.js # 打包配置(如果使用 Rollup)

2. 目录和文件说明

public/ 目录

  • index.html: 默认的 HTML 模板,Svelte 会将生成的 JavaScript 和 CSS 文件插入到该模板中。
  • favicon.ico: 网站图标。
  • global.css: 全局样式表,通常包含一些通用的样式,例如字体、页面背景色等。
  • 其他资源: 比如 robots.txtmanifest.json 等与前端资源有关的文件。

src/ 目录

  • **assets/**:存放静态资源(如图片、图标、字体文件等)。
  • components/:所有的 Svelte 组件文件(.svelte 文件)。这些组件通常是独立的功能模块,包含了 HTML、CSS 和 JavaScript。
    • 例如:Button.svelteHeader.svelte 等。
  • routes/:在使用 SvelteKit 时,这个目录存放页面组件,每个 .svelte文件对应一个路由。
    • 例如,src/routes/index.svelte 是首页,src/routes/about.svelte 是关于页。
    • 在 SvelteKit 中,路由是基于文件系统的,也就是说文件的路径直接对应于 URL 路径。
  • stores/:存放全局状态管理的 store文件。
    • Svelte 的 store 用于在多个组件之间共享状态,可以使用 writablereadablederived 等类型的 store 来管理应用的状态。
    • 例如:src/stores/countStore.js 可能包含一个 writable store 来管理计数器状态。
  • **app.css**:应用的基础样式。这里可以放置一些常见的样式设置,如全局布局、字体样式等。
  • **app.jsmain.js**:应用的入口文件,通常会引入 App.svelte 并进行挂载。

创建一个项目:

使用 create-svelte 工具创建项目
使用 pnpm 创建一个 Svelte 项目。执行以下命令:

1
npx sv create my-svelte-app

这条命令会创建一个新的 Svelte 项目,并将其命名为 my-svelte-app(你可以根据需要修改项目名)。你可以选择模板类型,如 Skeleton projectSvelteKit 等。

安装依赖
进入项目目录并安装依赖:

1
2
cd my-svelte-app
pnpm install

运行开发服务器
启动开发服务器:

1
pnpm run dev

持久化存储

在 Svelte 项目中,writable() store 仅在 内存中存储,当页面刷新时会被 重置
如果想要 刷新页面后仍然保留数据(比如 tokenuser),就必须用 本地存储(localStorage) 来持久化。


🌟 1. localStorage 是什么?

localStorage 是浏览器提供的本地存储,数据不会因页面刷新而丢失,除非手动清除或代码删除。

📌 特点

  • 存储容量大(最多 5MB)。
  • 不会随页面刷新而丢失
  • 只能存字符串(对象需要 JSON.stringify())。

📌 示例

1
2
3
localStorage.setItem("username", "张三"); // 存入
console.log(localStorage.getItem("username")); // 读取
localStorage.removeItem("username"); // 删除

🌟 2. Svelte stores 的问题

Svelte 的 writable()内存存储

1
2
import { writable } from "svelte/store";
export const user = writable(null); // 关闭页面就会丢失数据

🔴 问题:用户登录后,刷新页面 usernull,导致状态丢失。


🌟 3. 解决方案:封装 localStorage 持久化 stores

stores.js 里封装一个持久化的 Store,让它自动存入 localStorage

📍 路径src/lib/stores.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { writable } from "svelte/store";

// 1️⃣ 从 localStorage 读取数据
const storedUser = localStorage.getItem("user");

// 2️⃣ 创建 writable store,默认值为 localStorage 里的数据
export const user = writable(storedUser ? JSON.parse(storedUser) : null);

// 3️⃣ 监听 store 变化,并存入 localStorage
user.subscribe((value) => {
if (value) {
localStorage.setItem("user", JSON.stringify(value));
} else {
localStorage.removeItem("user"); // 退出时清除
}
});

📌 解释

  1. 初始值从 localStorage 读取,如果 localStorage 里有用户数据,就用它,否则 null

  2. 创建 writable() Store,用于存储用户信息。

  3. 监听 store 变化

    • 如果 user 变了,自动存入 localStorage
    • **如果 user 设为 null**(用户退出),清除 localStorage

🌟 4. 登录时存储 user

📍 路径src/routes/login/+page.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
import { goto } from "$app/navigation";
import api from "$lib/api";
import { user } from "$lib/stores";

let username = "";
let password = "";

async function login() {
try {
const res = await api.post("/api/login", { username, password });
user.set(res.data); // 这会自动存入 localStorage
alert("登录成功!");
goto("/");
} catch (err) {
alert(err.message || "登录失败");
}
}
</script>

<h1>登录</h1>
<input bind:value={username} placeholder="用户名" />
<input type="password" bind:value={password} placeholder="密码" />
<button on:click={login}>登录</button>

📌 这里 user.set(res.data) 会自动存入 localStorage

  • 刷新页面后 localStorage 仍然存在,所以 user 不会丢失。

🌟 5. 退出登录时清除 localStorage

📍 路径src/routes/profile/+page.svelte

1
2
3
4
5
6
7
8
9
10
11
<script>
import { goto } from "$app/navigation";
import { user } from "$lib/stores";

function logout() {
user.set(null); // 这会自动清除 localStorage
goto("/login");
}
</script>

<button on:click={logout}>退出登录</button>

📌 user.set(null) 会自动删除 localStorage 里的 user 数据!

  • 这样就不会留存登录信息,确保用户 退出后必须重新登录

🌟 6. API 请求时自动附带 token

axios 请求时,我们要**自动携带 token**,所以要封装 API 请求拦截器。

📍 路径src/lib/api.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import axios from "axios";
import { get } from "svelte/store";
import { user } from "$lib/stores";
import { goto } from "$app/navigation";

const baseURL = "http://big-event-vue-api-t.itheima.net";

const instance = axios.create({
baseURL,
timeout: 5000,
});

// 请求拦截器
instance.interceptors.request.use(
(config) => {
const userData = get(user);
if (userData?.token) {
config.headers.Authorization = userData.token;
}
return config;
},
(error) => Promise.reject(error)
);

// 响应拦截器
instance.interceptors.response.use(
(response) => {
if (response.data.code === 0) {
return response.data;
}
alert(response.data.message || "请求失败");
return Promise.reject(response);
},
(error) => {
const { response } = error;
if (!response) {
alert("请求失败,请检查网络连接");
return Promise.reject(error);
}
if (response.status === 401) {
user.set(null);
alert("登录已过期,请重新登录");
goto("/login");
} else {
alert(response.data?.message || "服务异常");
}
return Promise.reject(error);
}
);

export default instance;
export { baseURL };

📌 重点

  • **请求时自动携带 token**,从 stores.js 读取 user.token 并放入请求头。

  • 拦截 401 错误

    ,如果 token 失效:

    1. user.set(null) 清空用户信息
    2. goto("/login") 跳转到登录页

🌟 7. 页面刷新后仍然能保持登录

📍 路径src/routes/home/+page.svelte

1
2
3
4
5
6
7
8
9
10
11
<script>
import { user } from "$lib/stores";

let userData;
$: userData = $user; // 监听 user 变化
</script>

<h1>首页</h1>
{#if userData}
<p>欢迎, {userData.username}!</p>
{/if}

页面刷新后,userData 依然存在!


🌟 8. 总结

🔹 Svelte 默认 stores 不能持久化

  • 刷新页面就会丢失数据

🔹 解决方案

  1. 封装 stores.js,用 localStorage 读取 & 存储数据
  2. **用户登录后,user.set(res.data),会自动存入 localStorage**。
  3. **退出登录时 user.set(null),自动清空 localStorage**。
  4. 请求拦截器自动附带 token,保证接口安全
  5. 401 失效时,自动清除 user 并跳转到登录页

💡 这样就实现了真正的持久化存储!即使刷新页面也不会丢失登录状态! 🚀

notyf

notyf 是一个轻量级、现代化的通知库,提供了简单易用的 API 来展示通知。它支持自定义样式、自动消失、位置设置等功能。

安装 notyf

首先,你需要安装 notyf。如果你使用的是npm或yarn,可以通过以下命令安装:

1
npm install notyf

或者使用yarn:

1
yarn add notyf

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Notyf } from 'notyf';
import 'notyf/notyf.min.css'; // 引入样式

const notyf = new Notyf();

// 显示一个成功的通知
notyf.success('操作成功!');

// 显示一个错误的通知
notyf.error('操作失败!');

// 显示一个警告通知
notyf.open({
type: 'warning',
message: '这是一个警告通知'
});

配置选项

notyf 提供了许多配置选项来控制通知的显示方式。你可以在创建 Notyf 实例时传递一个配置对象来定制通知行为。

1
2
3
4
5
6
const notyf = new Notyf({
duration: 3000, // 持续时间(毫秒),通知消失前的显示时间
position: { x: 'right', y: 'top' }, // 通知的位置
dismissible: true, // 是否允许手动关闭通知
ripple: true // 是否添加点击波纹效果
});

自定义通知

你可以使用 open 方法来创建自定义通知。自定义通知支持传递多种类型的选项。

1
2
3
4
5
6
7
8
9
10
11
// 自定义信息通知
notyf.open({
type: 'custom',
background: '#6a5acd', // 背景颜色
message: '自定义通知',
icon: {
className: 'material-icons',
tag: 'span',
text: 'info'
}
});

显示不同类型的通知

notyf 支持显示几种不同类型的通知,主要有 success, error, 和 warning 类型。

1
2
3
4
5
6
7
8
// 成功通知
notyf.success('成功通知!');

// 错误通知
notyf.error('错误通知!');

// 警告通知
notyf.warning('警告通知!');

完整示例

以下是一个在Svelte组件中使用 notyf 的完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<script>
import { Notyf } from 'notyf';
import 'notyf/notyf.min.css';

const notyf = new Notyf();

function showSuccess() {
notyf.success('操作成功!');
}

function showError() {
notyf.error('操作失败!');
}

function showWarning() {
notyf.warning('这是一个警告通知');
}

function showCustomNotification() {
notyf.open({
type: 'custom',
background: '#FF6347',
message: '自定义通知!',
icon: { className: 'material-icons', tag: 'span', text: 'info' }
});
}
</script>

<button on:click={showSuccess}>成功通知</button>
<button on:click={showError}>错误通知</button>
<button on:click={showWarning}>警告通知</button>
<button on:click={showCustomNotification}>自定义通知</button>

总结

notyf 是一个简洁易用的通知库,适用于需要展示临时消息或通知的场景。它的API简单,能够快速集成进项目,并且提供了丰富的自定义选项,适应多种需求。

在 Svelte 中,获取当前路由的路径($route.path)通常是通过路由库来实现的,像是 svelte-routing@sveltejs/kit(对于 SvelteKit)。如果你在使用 SvelteKit,可以通过 $page 获取当前路径。

SvelteKit 中获取当前路径

在 SvelteKit 中,$page 是一个响应式的 store,它包含当前页面的信息,包括路径(path)和查询参数等。

你可以通过 $page.url.pathname 来获取当前路径。

示例代码:

1
2
3
4
5
6
7
8
9
<script>
import { page } from '$app/stores';
</script>

{#if $page.url.pathname === '/'}
<p>这是首页</p>
{:else}
<p>不是首页</p>
{/if}

svelte-routing 中获取路径

如果你在使用 svelte-routing,你可以通过 useLocation 钩子来获取当前路径。

示例代码:

1
2
3
4
5
6
7
8
9
10
<script>
import { useLocation } from 'svelte-routing';
const location = useLocation();
</script>

{#if location.pathname === '/'}
<p>这是首页</p>
{:else}
<p>不是首页</p>
{/if}

总结:

  • SvelteKit 中,使用 $page.url.pathname 来获取当前路径。
  • svelte-routing 中,使用 useLocation 钩子来获取当前路径。