MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Svelte项目结构:如何管理大型项目的路由与状态

2021-01-284.8k 阅读

一、Svelte 项目结构基础

在探讨大型项目的路由与状态管理之前,我们先来回顾一下 Svelte 项目的基本结构。一个典型的 Svelte 项目通常包含以下几个主要部分:

1. 项目根目录

项目根目录包含项目的核心配置文件,例如 package.json,它记录了项目的依赖包、脚本命令等重要信息。例如,在一个新创建的 Svelte 项目中,package.json 可能包含如下基础配置:

{
  "name": "my - svelte - project",
  "version": "0.0.1",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "@sveltejs/vite - plugin - svelte": "^3.0.0",
    "svelte": "^4.0.0",
    "vite": "^4.0.0"
  }
}

这里的 scripts 部分定义了开发、构建和预览项目的命令,而 devDependencies 列出了开发过程中需要的依赖包,如 @sveltejs/vite - plugin - svelte 用于在 Vite 中支持 Svelte,svelte 是核心框架,vite 是构建工具。

2. src 目录

src 目录是存放项目源代码的地方,这是项目的核心部分。它通常包含以下子目录和文件:

  • components 目录:这里存放各种 Svelte 组件。Svelte 组件是可复用的代码块,以 .svelte 为后缀。例如,一个简单的按钮组件 Button.svelte 可能如下:
<script>
  let text = 'Click me';
  let handleClick = () => {
    console.log('Button clicked');
  };
</script>

<button on:click={handleClick}>{text}</button>
  • routes 目录(如果使用路由):对于涉及路由的项目,routes 目录会存放与路由相关的组件,用于定义不同页面的内容。我们稍后会详细探讨路由相关内容。
  • lib 目录:该目录用于存放一些通用的库函数、工具类等。比如,你可能有一个 mathUtils.js 文件,里面定义了一些数学计算的辅助函数:
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
  • main.js:这是项目的入口文件,它负责初始化 Svelte 应用并挂载到 DOM 上。例如:
import { render } from'svelte';
import App from './App.svelte';

render(<App />, document.getElementById('app'));

这里通过 render 函数将 App.svelte 组件渲染到 idapp 的 DOM 元素上。

二、Svelte 中的路由管理

1. 路由库的选择

在 Svelte 项目中,有几种流行的路由库可供选择,其中 svelte - routingsvelte - kit 的路由系统是较为常用的。

svelte - routing:这是一个专门为 Svelte 设计的路由库,它提供了简洁的 API 来定义和管理路由。安装方式为 npm install svelte - routing

svelte - kit:SvelteKit 是 Svelte 的官方框架,它不仅提供了路由功能,还集成了构建、服务器端渲染等一系列强大的功能。使用 SvelteKit 创建项目非常简单,通过 npm create svelte@latest 命令即可初始化一个项目,其路由系统基于文件系统,具有直观的结构。在本文中,我们将以 SvelteKit 为例进行详细讲解,因为它在大型项目中的扩展性和集成性更好。

2. SvelteKit 的路由系统

SvelteKit 的路由系统基于文件系统,在 src/routes 目录下,每个文件和目录都对应一个路由。

  • 基本路由:例如,在 src/routes/home.svelte 中创建一个组件,它将对应 /home 路由。组件代码可能如下:
<script>
  // 组件逻辑
</script>

<h1>Home Page</h1>
<p>This is the home page of our SvelteKit application.</p>
  • 嵌套路由:SvelteKit 支持嵌套路由,通过在目录中创建 +page.svelte 文件来实现。例如,src/routes/products/+page.svelte 可以作为产品列表页面,而 src/routes/products/[productId]/+page.svelte 可以显示单个产品的详细信息。这里的 [productId] 是一个动态参数。
// src/routes/products/[productId]/+page.svelte
<script context="module">
  export function load({ params }) {
    return {
      productId: params.productId
    };
  }
</script>

<script>
  const { productId } = $page.data;
  // 根据 productId 获取产品详细信息的逻辑
</script>

<h1>Product {productId}</h1>
<p>Details of product {productId}</p>
  • 路由导航:在 SvelteKit 中,可以使用 <a> 标签进行路由导航。例如,要导航到首页,可以这样写:
<a href="/home">Go to Home</a>

也可以使用 svelte - kit 提供的 navigate 函数进行编程式导航。首先需要导入 navigate

import { navigate } from '$app/navigation';

然后在需要导航的地方调用 navigate,比如在按钮点击事件中:

<script>
  import { navigate } from '$app/navigation';
  let handleClick = () => {
    navigate('/products');
  };
</script>

<button on:click={handleClick}>Go to Products</button>

3. 处理路由状态

在路由切换过程中,有时需要处理一些状态,比如加载状态。SvelteKit 提供了方便的方式来实现这一点。

  • 加载状态:在 +page.js 文件(与 +page.svelte 对应的 JavaScript 文件,用于处理页面加载逻辑)中,可以通过返回一个 Promise 来表示加载状态。例如:
export async function load() {
  // 模拟异步数据加载
  await new Promise(resolve => setTimeout(resolve, 1000));
  return {
    data: 'Loaded data'
  };
}

+page.svelte 中,可以通过 $page 变量来获取加载状态:

<script>
  const { data, error, pending } = $page;
</script>

{#if pending}
  <p>Loading...</p>
{:else if error}
  <p>Error: {error.message}</p>
{:else}
  <p>{data}</p>
{/if}

这里通过 pending 判断是否正在加载,error 判断是否加载出错,data 获取加载的数据。

三、Svelte 中的状态管理

1. 局部状态

在 Svelte 组件中,局部状态非常容易管理。可以直接在组件的 <script> 标签中定义变量来表示状态。例如,在一个计数器组件中:

<script>
  let count = 0;
  let increment = () => {
    count++;
  };
</script>

<p>Count: {count}</p>
<button on:click={increment}>Increment</button>

这里的 count 就是该组件的局部状态,increment 函数用于更新这个状态。

2. 全局状态管理

对于大型项目,仅靠局部状态是不够的,需要一个全局状态管理方案。在 Svelte 中,有几种常见的方式来实现全局状态管理。

  • 使用 Svelte store:Svelte 内置了 store 概念,非常适合简单的全局状态管理。首先,在 src/lib 目录下创建一个 store.js 文件:
import { writable } from'svelte/store';

export const userStore = writable({
  name: '',
  age: 0
});

然后在组件中使用这个 store:

<script>
  import { userStore } from '$lib/store';
  const { subscribe, update } = userStore;
  let name;
  let age;
  subscribe(({ name: n, age: a }) => {
    name = n;
    age = a;
  });
  let updateUser = () => {
    update(userStore, user => {
      user.name = 'New Name';
      user.age = 25;
      return user;
    });
  };
</script>

<p>User Name: {name}</p>
<p>User Age: {age}</p>
<button on:click={updateUser}>Update User</button>

这里通过 subscribe 订阅 userStore 的变化,update 函数用于更新 userStore 的状态。

  • 使用第三方状态管理库:如 MobX - SvelteRedux - Svelte。以 MobX - Svelte 为例,首先安装 mobxmobx - sveltenpm install mobx mobx - svelte

src/lib 目录下创建一个 mobxStore.js 文件:

import { makeObservable, observable, action } from'mobx';

class AppStore {
  constructor() {
    this.counter = 0;
    makeObservable(this, {
      counter: observable,
      increment: action
    });
  }
  increment() {
    this.counter++;
  }
}

export const appStore = new AppStore();

在组件中使用 MobX - Svelte

<script>
  import { useObserver } from'mobx - svelte';
  import { appStore } from '$lib/mobxStore';
</script>

{#if useObserver(() => true)}
  <p>Counter: {appStore.counter}</p>
  <button on:click={() => appStore.increment()}>Increment</button>
{/if}

这里通过 useObserver 函数来观察 appStore 的变化并更新组件视图。

3. 状态与路由的结合

在大型项目中,状态和路由往往是紧密相关的。例如,用户登录状态可能影响某些路由的访问权限。

  • 基于状态的路由导航:假设我们有一个用户登录状态的 store,在导航到某些需要登录的页面时,先检查登录状态。
<script>
  import { navigate } from '$app/navigation';
  import { userStore } from '$lib/store';
  const { subscribe } = userStore;
  let isLoggedIn;
  subscribe(user => {
    isLoggedIn = user.name!== '';
  });
  let goToProfile = () => {
    if (isLoggedIn) {
      navigate('/profile');
    } else {
      console.log('Please log in first');
    }
  };
</script>

<button on:click={goToProfile}>Go to Profile</button>
  • 路由变化更新状态:当路由切换时,也可能需要更新某些状态。比如,在不同页面可能有不同的页面标题,我们可以在路由的 load 函数中更新全局的页面标题状态。
// src/routes/about/+page.js
import { pageTitleStore } from '$lib/store';

export async function load() {
  pageTitleStore.update(title => 'About Page');
  return {};
}

四、大型项目中的路由与状态管理实践

1. 项目结构优化

在大型项目中,合理的项目结构对于路由和状态管理至关重要。

  • 按功能模块划分目录:除了基本的 src/routessrc/components 目录外,可以按功能模块进一步细分。例如,对于一个电商项目,可以有 src/modules/productssrc/modules/cartsrc/modules/checkout 等目录。每个模块目录下可以包含各自的路由、组件和状态管理相关代码。
src/
├── modules/
│   ├── products/
│   │   ├── routes/
│   │   │   ├── +page.svelte
│   │   │   ├── [productId]/+page.svelte
│   │   ├── components/
│   │   │   ├── ProductList.svelte
│   │   │   ├── ProductDetail.svelte
│   │   ├── store.js
│   ├── cart/
│   │   ├── routes/
│   │   │   ├── +page.svelte
│   │   ├── components/
│   │   │   ├── CartItem.svelte
│   │   │   ├── CartSummary.svelte
│   │   ├── store.js
│   ├── checkout/
│   │   ├── routes/
│   │   │   ├── +page.svelte
│   │   ├── components/
│   │   │   ├── CheckoutForm.svelte
│   │   ├── store.js
├── routes/
│   ├── home.svelte
│   ├── about.svelte
├── components/
│   ├── Layout.svelte
│   ├── Navbar.svelte
├── lib/
│   ├── utils.js
│   ├── api.js
├── main.js
  • 共享状态与路由配置:对于一些共享的状态,如用户登录状态、全局配置等,可以放在 src/lib/store 目录下统一管理。同时,在路由的 load 函数中,可以根据共享状态进行一些初始化操作或权限检查。例如:
// src/routes/admin/+page.js
import { userStore } from '$lib/store';

export async function load() {
  const { name } = $userStore;
  if (name!== 'admin') {
    throw new Error('Access denied');
  }
  return {};
}

2. 代码复用与模块化

在管理大型项目的路由和状态时,代码复用和模块化是提高开发效率的关键。

  • 路由组件复用:在 SvelteKit 中,可以通过提取公共的路由组件来实现复用。例如,一个带有侧边栏的页面布局,可以将侧边栏和主体内容分离为不同的组件。
// src/components/LayoutWithSidebar.svelte
<script>
  // 布局相关逻辑
</script>

<div class="layout">
  <Sidebar />
  <div class="main - content">
    {#if $page.route.id === '/admin'}
      <AdminContent />
    {:else if $page.route.id === '/products'}
      <ProductList />
    {/if}
  </div>
</div>
  • 状态管理模块复用:对于状态管理,不同模块可能需要一些相同的状态管理逻辑。比如,在多个模块中都需要处理加载状态,可以将加载状态管理的逻辑封装成一个函数或类。
// src/lib/loadingUtils.js
import { writable } from'svelte/store';

export const createLoadingStore = () => {
  const loading = writable(false);
  const startLoading = () => loading.set(true);
  const stopLoading = () => loading.set(false);
  return {
    loading,
    startLoading,
    stopLoading
  };
};

然后在不同模块的状态管理中使用这个工具:

// src/modules/products/store.js
import { createLoadingStore } from '$lib/loadingUtils';

const { loading, startLoading, stopLoading } = createLoadingStore();

// 其他产品模块的状态管理逻辑

3. 性能优化

在大型项目中,性能优化对于路由和状态管理同样重要。

  • 路由懒加载:SvelteKit 支持路由懒加载,通过在 +page.js 文件中使用动态导入来实现。例如:
export async function load() {
  const { default: data } = await import('./data.json');
  return {
    data
  };
}

这样只有在访问该路由时才会加载相关数据,提高了应用的初始加载速度。

  • 状态更新优化:在状态管理中,避免不必要的状态更新。例如,在使用 Svelte store 时,确保 update 函数中只更新真正需要变化的部分。
// 不好的做法
update(userStore, user => {
  user.newProperty = 'new value';
  return user;
});

// 好的做法,只更新变化的部分
update(userStore, user => ({
 ...user,
  newProperty: 'new value'
}));

这样可以减少不必要的视图更新,提高应用的性能。

通过以上对 Svelte 项目中路由与状态管理的深入探讨和实践指导,希望能帮助开发者更好地构建和管理大型 Svelte 项目,提高项目的可维护性、扩展性和性能。在实际开发过程中,根据项目的具体需求和特点,灵活选择和组合这些方法,以达到最佳的开发效果。