Store状态管理 #

一、Store 概述 #

Store 是 Svelte 的跨组件状态管理方案,用于在组件树中共享状态。

1.1 为什么需要 Store #

text
┌─────────────────────────────────────────────────────────────┐
│                    组件通信问题                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Props 传递(深层嵌套)                                      │
│  ┌─────────┐                                                │
│  │  App    │                                                │
│  │  user   │                                                │
│  └────┬────┘                                                │
│       ↓ props                                               │
│  ┌─────────┐                                                │
│  │ Header  │                                                │
│  └────┬────┘                                                │
│       ↓ props                                               │
│  ┌─────────┐                                                │
│  │ Nav     │                                                │
│  └────┬────┘                                                │
│       ↓ props                                               │
│  ┌─────────┐                                                │
│  │ UserMenu│ ← 需要user                                     │
│  └─────────┘                                                │
│                                                             │
│  Store 解决方案                                              │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐                       │
│  │  App    │ │ Header  │ │ UserMenu│                       │
│  └────┬────┘ └────┬────┘ └────┬────┘                       │
│       └───────────┴───────────┘                             │
│               ↓                                             │
│       ┌─────────────┐                                       │
│       │ userStore   │ ← 共享状态                            │
│       └─────────────┘                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 Store 契约 #

任何对象只要满足以下契约,就是有效的 Store:

javascript
const store = {
  subscribe: (subscriber) => {
    subscriber(currentValue);
    return unsubscribeFunction;
  }
};

二、Writable Store #

2.1 创建 Writable Store #

javascript
import { writable } from 'svelte/store';

const count = writable(0);

const user = writable({
  name: 'Alice',
  age: 25
});

2.2 使用 Store #

svelte
<script>
  import { count } from './stores.js';
  
  function increment() {
    count.update(n => n + 1);
  }
  
  function setToTen() {
    count.set(10);
  }
</script>

<button onclick={increment}>
  Count: {$count}
</button>
<button onclick={setToTen}>Set to 10</button>

2.3 Store 方法 #

方法 说明
subscribe(fn) 订阅变化,返回取消订阅函数
set(value) 设置新值
update(fn) 基于当前值更新

2.4 自动订阅 ($语法) #

svelte
<script>
  import { count } from './stores.js';
</script>

<p>{$count}</p>

<button onclick={() => count.update(n => n + 1)}>+</button>

2.5 完整示例 #

javascript
import { writable } from 'svelte/store';

export const todos = writable([]);

export function addTodo(text) {
  todos.update(items => [...items, {
    id: Date.now(),
    text,
    done: false
  }]);
}

export function toggleTodo(id) {
  todos.update(items => items.map(item =>
    item.id === id ? { ...item, done: !item.done } : item
  ));
}

export function removeTodo(id) {
  todos.update(items => items.filter(item => item.id !== id));
}
svelte
<script>
  import { todos, addTodo, toggleTodo, removeTodo } from './stores.js';
  
  let newTodo = '';
  
  function handleSubmit(e) {
    e.preventDefault();
    if (newTodo.trim()) {
      addTodo(newTodo.trim());
      newTodo = '';
    }
  }
</script>

<form onsubmit={handleSubmit}>
  <input bind:value={newTodo} placeholder="Add todo" />
  <button type="submit">Add</button>
</form>

<ul>
  {#each $todos as todo}
    <li class:done={todo.done}>
      <input 
        type="checkbox" 
        checked={todo.done}
        onchange={() => toggleTodo(todo.id)}
      />
      <span>{todo.text}</span>
      <button onclick={() => removeTodo(todo.id)}>×</button>
    </li>
  {/each}
</ul>

三、Readable Store #

3.1 创建 Readable Store #

javascript
import { readable } from 'svelte/store';

export const time = readable(new Date(), (set) => {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);
  
  return () => clearInterval(interval);
});

3.2 使用示例 #

svelte
<script>
  import { time } from './stores.js';
</script>

<p>当前时间: {$time.toLocaleTimeString()}</p>

3.3 只读配置 #

javascript
import { readable } from 'svelte/store';

export const config = readable({
  apiUrl: 'https://api.example.com',
  version: '1.0.0',
  features: ['feature1', 'feature2']
});

四、Derived Store #

4.1 创建 Derived Store #

javascript
import { writable, derived } from 'svelte/store';

const count = writable(0);

export const doubled = derived(count, $count => $count * 2);

export const squared = derived(count, $count => $count * $count);

4.2 多源派生 #

javascript
import { writable, derived } from 'svelte/store';

const firstName = writable('John');
const lastName = writable('Doe');

export const fullName = derived(
  [firstName, lastName],
  ([$firstName, $lastName]) => `${$firstName} ${$lastName}`
);

4.3 异步派生 #

javascript
import { writable, derived } from 'svelte/store';

const userId = writable(1);

export const user = derived(userId, async ($userId, set) => {
  set({ loading: true });
  
  try {
    const response = await fetch(`/api/users/${$userId}`);
    const data = await response.json();
    set({ loading: false, data });
  } catch (error) {
    set({ loading: false, error: error.message });
  }
});

4.4 完整购物车示例 #

javascript
import { writable, derived } from 'svelte/store';

export const cart = writable([]);

export const totalItems = derived(cart, $cart => 
  $cart.reduce((sum, item) => sum + item.quantity, 0)
);

export const totalPrice = derived(cart, $cart =>
  $cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
);

export const discount = derived(totalPrice, $totalPrice =>
  $totalPrice > 100 ? $totalPrice * 0.1 : 0
);

export const finalPrice = derived(
  [totalPrice, discount],
  ([$totalPrice, $discount]) => $totalPrice - $discount
);

export function addToCart(product) {
  cart.update(items => {
    const existing = items.find(i => i.id === product.id);
    if (existing) {
      return items.map(i =>
        i.id === product.id
          ? { ...i, quantity: i.quantity + 1 }
          : i
      );
    }
    return [...items, { ...product, quantity: 1 }];
  });
}

export function removeFromCart(id) {
  cart.update(items => items.filter(i => i.id !== id));
}

export function updateQuantity(id, quantity) {
  cart.update(items =>
    items.map(i =>
      i.id === id ? { ...i, quantity } : i
    )
  );
}

五、自定义 Store #

5.1 封装逻辑 #

javascript
import { writable } from 'svelte/store';

function createCount() {
  const { subscribe, set, update } = writable(0);
  
  return {
    subscribe,
    increment: () => update(n => n + 1),
    decrement: () => update(n => n - 1),
    reset: () => set(0)
  };
}

export const count = createCount();

5.2 使用自定义 Store #

svelte
<script>
  import { count } from './stores.js';
</script>

<p>{$count}</p>
<button onclick={() => count.increment()}>+</button>
<button onclick={() => count.decrement()}>-</button>
<button onclick={() => count.reset()}>Reset</button>

5.3 带持久化的 Store #

javascript
import { writable } from 'svelte/store';

function persistent(key, initialValue) {
  const stored = localStorage.getItem(key);
  const value = stored ? JSON.parse(stored) : initialValue;
  
  const store = writable(value);
  
  store.subscribe($value => {
    localStorage.setItem(key, JSON.stringify($value));
  });
  
  return store;
}

export const todos = persistent('todos', []);
export const settings = persistent('settings', {
  theme: 'light',
  fontSize: 14
});

5.4 带历史记录的 Store #

javascript
import { writable, derived } from 'svelte/store';

function withHistory(initialValue, maxHistory = 10) {
  const { subscribe, set, update } = writable(initialValue);
  const history = writable([initialValue]);
  const index = writable(0);
  
  function push(value) {
    history.update(h => {
      const newIndex = h.length;
      const newHistory = [...h.slice(0, newIndex), value].slice(-maxHistory);
      return newHistory;
    });
    index.set(history.get().length - 1);
  }
  
  return {
    subscribe,
    set: (value) => {
      set(value);
      push(value);
    },
    update: (fn) => {
      update(value => {
        const newValue = fn(value);
        push(newValue);
        return newValue;
      });
    },
    undo: () => {
      index.update(i => Math.max(0, i - 1));
      const h = history.get();
      const i = index.get();
      set(h[i]);
    },
    redo: () => {
      index.update(i => Math.min(history.get().length - 1, i + 1));
      const h = history.get();
      const i = index.get();
      set(h[i]);
    },
    canUndo: derived(index, $i => $i > 0),
    canRedo: derived([history, index], ([$h, $i]) => $i < $h.length - 1)
  };
}

六、Store 绑定 #

6.1 双向绑定 #

svelte
<script>
  import { writable } from 'svelte/store';
  
  const name = writable('World');
</script>

<input bind:value={$name} />
<p>Hello {$name}!</p>

6.2 表单绑定 #

svelte
<script>
  import { writable } from 'svelte/store';
  
  const form = writable({
    email: '',
    password: '',
    remember: false
  });
  
  function handleSubmit() {
    console.log($form);
  }
</script>

<form onsubmit|preventDefault={handleSubmit}>
  <input type="email" bind:value={$form.email} />
  <input type="password" bind:value={$form.password} />
  <label>
    <input type="checkbox" bind:checked={$form.remember} />
    Remember me
  </label>
  <button type="submit">Submit</button>
</form>

七、Store 模式 #

7.1 模块化 Store #

javascript
import { writable, derived } from 'svelte/store';

function createUserStore() {
  const { subscribe, set, update } = writable(null);
  
  return {
    subscribe,
    login: async (credentials) => {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials)
      });
      const user = await response.json();
      set(user);
      return user;
    },
    logout: async () => {
      await fetch('/api/logout', { method: 'POST' });
      set(null);
    },
    updateProfile: (data) => {
      update(user => ({ ...user, ...data }));
    }
  };
}

export const user = createUserStore();

7.2 服务层模式 #

javascript
import { writable, derived } from 'svelte/store';

function createApiStore(endpoint) {
  const data = writable(null);
  const loading = writable(false);
  const error = writable(null);
  
  async function fetch(params = {}) {
    loading.set(true);
    error.set(null);
    
    try {
      const query = new URLSearchParams(params).toString();
      const response = await fetch(`${endpoint}?${query}`);
      const result = await response.json();
      data.set(result);
    } catch (e) {
      error.set(e.message);
    } finally {
      loading.set(false);
    }
  }
  
  return {
    data: { subscribe: data.subscribe },
    loading: { subscribe: loading.subscribe },
    error: { subscribe: error.subscribe },
    fetch
  };
}

export const users = createApiStore('/api/users');
export const products = createApiStore('/api/products');

八、Svelte 5 Runes 与 Store #

8.1 使用 $state 替代简单 Store #

svelte
<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
</script>

8.2 跨组件共享状态 #

javascript
export const counter = $state(0);
svelte
<script>
  import { counter } from './state.js';
</script>

<button onclick={() => counter++}>
  Count: {counter}
</button>

8.3 使用 $effect 同步 #

svelte
<script>
  import { counter } from './state.js';
  
  $effect(() => {
    localStorage.setItem('counter', counter);
  });
</script>

九、完整示例:主题切换 #

javascript
import { writable, derived } from 'svelte/store';

const THEME_KEY = 'app-theme';

function createThemeStore() {
  const stored = localStorage.getItem(THEME_KEY) || 'light';
  const { subscribe, set } = writable(stored);
  
  function updateTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem(THEME_KEY, theme);
    set(theme);
  }
  
  updateTheme(stored);
  
  return {
    subscribe,
    toggle: () => {
      let current;
      const unsub = subscribe(t => current = t);
      unsub();
      updateTheme(current === 'light' ? 'dark' : 'light');
    },
    set: updateTheme
  };
}

export const theme = createThemeStore();

export const isDark = derived(theme, $theme => $theme === 'dark');
svelte
<script>
  import { theme, isDark } from './stores/theme.js';
</script>

<button onclick={() => theme.toggle()}>
  {#if $isDark}
    🌙 Dark
  {:else}
    ☀️ Light
  {/if}
</button>

十、总结 #

Store 类型 用途
writable 可读写状态
readable 只读状态
derived 派生状态
自定义 Store 封装业务逻辑

Store 使用要点:

  • 使用 $ 语法自动订阅
  • onDestroy 中手动取消订阅
  • 派生 Store 自动追踪依赖
  • 自定义 Store 封装业务逻辑
  • 合理拆分 Store 模块
最后更新:2026-03-28