第一个Svelte应用 #

一、创建项目 #

1.1 初始化项目 #

bash
npm create vite@latest my-first-svelte -- --template svelte

cd my-first-svelte
npm install
npm run dev

1.2 项目结构 #

text
my-first-svelte/
├── src/
│   ├── lib/
│   │   └── Counter.svelte
│   ├── App.svelte
│   ├── app.css
│   └── main.js
├── index.html
└── package.json

二、理解组件 #

2.1 App.svelte 结构 #

打开 src/App.svelte,你会看到 Svelte 组件的基本结构:

svelte
<script>
  let count = 0;
</script>

<main>
  <h1>Hello Svelte!</h1>
  <p>Count: {count}</p>
</main>

<style>
  main {
    text-align: center;
    padding: 2rem;
  }
  
  h1 {
    color: #ff3e00;
  }
</style>

2.2 组件三要素 #

text
┌─────────────────────────────────────────┐
│            Svelte 组件                   │
├─────────────────────────────────────────┤
│  <script>    JavaScript 逻辑            │
│  ...                                     │
│  </script>                               │
├─────────────────────────────────────────┤
│  HTML 模板                               │
│  <div>...</div>                          │
├─────────────────────────────────────────┤
│  <style>     CSS 样式(自动作用域)       │
│  ...                                     │
│  </style>                                │
└─────────────────────────────────────────┘

三、构建计数器应用 #

3.1 基础计数器 #

修改 src/App.svelte

svelte
<script>
  let count = 0;
  
  function increment() {
    count += 1;
  }
  
  function decrement() {
    count -= 1;
  }
  
  function reset() {
    count = 0;
  }
</script>

<main>
  <h1>计数器</h1>
  <p class="count">{count}</p>
  
  <div class="buttons">
    <button on:click={decrement}>-</button>
    <button on:click={reset}>重置</button>
    <button on:click={increment}>+</button>
  </div>
</main>

<style>
  main {
    text-align: center;
    padding: 2rem;
  }
  
  h1 {
    color: #333;
    margin-bottom: 1rem;
  }
  
  .count {
    font-size: 4rem;
    font-weight: bold;
    color: #ff3e00;
    margin: 1rem 0;
  }
  
  .buttons {
    display: flex;
    gap: 1rem;
    justify-content: center;
  }
  
  button {
    padding: 0.5rem 1.5rem;
    font-size: 1.2rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    background: #ff3e00;
    color: white;
    transition: background 0.2s;
  }
  
  button:hover {
    background: #e63600;
  }
</style>

3.2 添加响应式声明 #

svelte
<script>
  let count = 0;
  
  $: doubled = count * 2;
  $: isPositive = count > 0;
  $: isNegative = count < 0;
  
  function increment() {
    count += 1;
  }
  
  function decrement() {
    count -= 1;
  }
  
  function reset() {
    count = 0;
  }
</script>

<main>
  <h1>计数器</h1>
  <p class="count" class:positive={isPositive} class:negative={isNegative}>
    {count}
  </p>
  <p class="info">双倍值: {doubled}</p>
  
  <div class="buttons">
    <button on:click={decrement}>-</button>
    <button on:click={reset}>重置</button>
    <button on:click={increment}>+</button>
  </div>
</main>

<style>
  .positive { color: green; }
  .negative { color: red; }
  .info { color: #666; }
</style>

3.3 Svelte 5 Runes 版本 #

svelte
<script>
  let count = $state(0);
  
  let doubled = $derived(count * 2);
  let isPositive = $derived(count > 0);
  let isNegative = $derived(count < 0);
  
  function increment() {
    count += 1;
  }
  
  function decrement() {
    count -= 1;
  }
  
  function reset() {
    count = 0;
  }
  
  $effect(() => {
    document.title = `Count: ${count}`;
  });
</script>

<main>
  <h1>计数器</h1>
  <p class="count" class:positive={isPositive} class:negative={isNegative}>
    {count}
  </p>
  <p class="info">双倍值: {doubled}</p>
  
  <div class="buttons">
    <button onclick={decrement}>-</button>
    <button onclick={reset}>重置</button>
    <button onclick={increment}>+</button>
  </div>
</main>

四、添加更多功能 #

4.1 步进值控制 #

svelte
<script>
  let count = 0;
  let step = 1;
  
  $: doubled = count * 2;
  
  function increment() {
    count += step;
  }
  
  function decrement() {
    count -= step;
  }
  
  function reset() {
    count = 0;
  }
</script>

<main>
  <h1>计数器</h1>
  
  <label>
    步进值:
    <input type="number" bind:value={step} min="1" max="10" />
  </label>
  
  <p class="count">{count}</p>
  <p class="info">双倍值: {doubled}</p>
  
  <div class="buttons">
    <button on:click={decrement}>-{step}</button>
    <button on:click={reset}>重置</button>
    <button on:click={increment}>+{step}</button>
  </div>
</main>

<style>
  label {
    display: block;
    margin-bottom: 1rem;
  }
  
  input {
    width: 60px;
    padding: 0.3rem;
    font-size: 1rem;
    margin-left: 0.5rem;
  }
</style>

4.2 历史记录功能 #

svelte
<script>
  let count = 0;
  let history = [];
  
  function recordAction(action) {
    history = [...history, { action, value: count, time: new Date() }];
  }
  
  function increment() {
    count += 1;
    recordAction('increment');
  }
  
  function decrement() {
    count -= 1;
    recordAction('decrement');
  }
  
  function reset() {
    count = 0;
    recordAction('reset');
  }
  
  function clearHistory() {
    history = [];
  }
</script>

<main>
  <h1>计数器</h1>
  <p class="count">{count}</p>
  
  <div class="buttons">
    <button on:click={decrement}>-</button>
    <button on:click={reset}>重置</button>
    <button on:click={increment}>+</button>
  </div>
  
  {#if history.length > 0}
    <div class="history">
      <h3>操作历史</h3>
      <button on:click={clearHistory}>清除历史</button>
      <ul>
        {#each history as item}
          <li>{item.action}: {item.value} ({item.time.toLocaleTimeString()})</li>
        {/each}
      </ul>
    </div>
  {/if}
</main>

五、创建可复用组件 #

5.1 创建 Button 组件 #

src/lib/Button.svelte

svelte
<script>
  let { 
    type = 'default',
    disabled = false,
    onclick 
  } = $props();
</script>

<button 
  class={`btn btn-${type}`}
  {disabled}
  {onclick}
>
  <slot />
</button>

<style>
  .btn {
    padding: 0.5rem 1.5rem;
    font-size: 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.2s;
  }
  
  .btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
  
  .btn-default {
    background: #ff3e00;
    color: white;
  }
  
  .btn-default:hover:not(:disabled) {
    background: #e63600;
  }
  
  .btn-secondary {
    background: #666;
    color: white;
  }
  
  .btn-secondary:hover:not(:disabled) {
    background: #555;
  }
</style>

5.2 使用 Button 组件 #

svelte
<script>
  import Button from './lib/Button.svelte';
  
  let count = 0;
  
  function increment() {
    count += 1;
  }
  
  function decrement() {
    count -= 1;
  }
  
  function reset() {
    count = 0;
  }
</script>

<main>
  <h1>计数器</h1>
  <p class="count">{count}</p>
  
  <div class="buttons">
    <Button onclick={decrement}>-</Button>
    <Button type="secondary" onclick={reset}>重置</Button>
    <Button onclick={increment}>+</Button>
  </div>
</main>

六、完整示例 #

6.1 完整计数器应用 #

svelte
<script>
  import Button from './lib/Button.svelte';
  
  let count = $state(0);
  let step = $state(1);
  let history = $state([]);
  
  let doubled = $derived(count * 2);
  let isPositive = $derived(count > 0);
  let isNegative = $derived(count < 0);
  let isZero = $derived(count === 0);
  
  function recordAction(action) {
    history = [...history, { 
      action, 
      value: count, 
      time: new Date() 
    }];
  }
  
  function increment() {
    count += step;
    recordAction('increment');
  }
  
  function decrement() {
    count -= step;
    recordAction('decrement');
  }
  
  function reset() {
    count = 0;
    recordAction('reset');
  }
  
  function clearHistory() {
    history = [];
  }
  
  $effect(() => {
    document.title = `计数器: ${count}`;
  });
</script>

<main>
  <h1>🔢 计数器应用</h1>
  
  <div class="controls">
    <label>
      步进值:
      <input type="number" bind:value={step} min="1" max="10" />
    </label>
  </div>
  
  <p 
    class="count" 
    class:positive={isPositive} 
    class:negative={isNegative}
    class:zero={isZero}
  >
    {count}
  </p>
  
  <p class="info">双倍值: {doubled}</p>
  
  <div class="buttons">
    <Button onclick={decrement}>-{step}</Button>
    <Button type="secondary" onclick={reset} disabled={isZero}>重置</Button>
    <Button onclick={increment}>+{step}</Button>
  </div>
  
  {#if history.length > 0}
    <div class="history">
      <h3>
        操作历史 
        <Button type="secondary" onclick={clearHistory}>清除</Button>
      </h3>
      <ul>
        {#each history.slice(-5) as item}
          <li>
            <span class="action">{item.action}</span>
            <span class="value">{item.value}</span>
            <span class="time">{item.time.toLocaleTimeString()}</span>
          </li>
        {/each}
      </ul>
    </div>
  {/if}
</main>

<style>
  main {
    max-width: 400px;
    margin: 2rem auto;
    padding: 2rem;
    text-align: center;
    background: #f5f5f5;
    border-radius: 8px;
  }
  
  h1 {
    color: #333;
    margin-bottom: 1.5rem;
  }
  
  .controls {
    margin-bottom: 1rem;
  }
  
  label {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
  }
  
  input {
    width: 60px;
    padding: 0.3rem;
    font-size: 1rem;
    border: 1px solid #ccc;
    border-radius: 4px;
  }
  
  .count {
    font-size: 4rem;
    font-weight: bold;
    margin: 1rem 0;
    transition: color 0.3s;
  }
  
  .positive { color: #22c55e; }
  .negative { color: #ef4444; }
  .zero { color: #666; }
  
  .info {
    color: #888;
    margin-bottom: 1.5rem;
  }
  
  .buttons {
    display: flex;
    gap: 0.5rem;
    justify-content: center;
    margin-bottom: 1.5rem;
  }
  
  .history {
    text-align: left;
    padding: 1rem;
    background: white;
    border-radius: 4px;
    margin-top: 1rem;
  }
  
  .history h3 {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.5rem;
  }
  
  .history ul {
    list-style: none;
    padding: 0;
    margin: 0;
  }
  
  .history li {
    display: flex;
    gap: 1rem;
    padding: 0.3rem 0;
    border-bottom: 1px solid #eee;
    font-size: 0.9rem;
  }
  
  .action { color: #ff3e00; }
  .value { font-weight: bold; }
  .time { color: #999; margin-left: auto; }
</style>

七、运行项目 #

7.1 启动开发服务器 #

bash
npm run dev

7.2 访问应用 #

打开浏览器访问 http://localhost:5173

八、总结 #

本节我们学习了:

知识点 说明
组件结构 script、template、style 三部分
响应式声明 $:$state/$derived
事件处理 on:clickonclick
条件渲染 {#if}...{/if}
列表渲染 {#each}...{/each}
样式作用域 style 标签内的样式自动作用域化
组件复用 通过 props 和 slot 实现组件复用
最后更新:2026-03-28