状态管理 (Stores)
概述
BiliBili ShadowReplay 使用 Svelte 的响应式存储 (Stores) 来管理全局状态。所有 stores 位于 src/lib/stores/ 目录。
Svelte Stores 基础
Svelte 提供三种基本的 store 类型:
- writable: 可读写的存储
- readable: 只读存储
- derived: 派生存储,基于其他 stores 计算得出
在组件中使用 $ 前缀可以自动订阅和解除订阅 store。
typescript
import { writable } from 'svelte/store';
// 创建 store
export const count = writable(0);
// 在组件中使用
<script>
import { count } from './stores';
// $count 会自动订阅并在值变化时更新
</script>
<p>Count: {$count}</p>主要 Stores
应用状态 Stores
用于管理应用级别的全局状态,如用户配置、主题设置等。
录制状态 Stores
管理录制任务的状态,包括:
- 当前录制任务列表
- 录制进度
- 录制状态 (录制中/已停止/错误)
直播间 Stores
管理直播间信息:
- 直播间列表
- 直播间状态 (在线/离线)
- 直播间配置
任务 Stores
管理后台任务状态:
- 任务队列
- 任务进度
- 任务结果
使用模式
订阅 Store
typescript
import { myStore } from '$lib/stores';
// 方式 1: 在组件中使用 $ 前缀 (推荐)
<script>
import { myStore } from '$lib/stores';
</script>
<div>{$myStore}</div>
// 方式 2: 手动订阅
const unsubscribe = myStore.subscribe(value => {
console.log(value);
});
// 记得在组件销毁时取消订阅
onDestroy(unsubscribe);更新 Store
typescript
import { writable } from 'svelte/store';
const count = writable(0);
// 方式 1: set 方法
count.set(1);
// 方式 2: update 方法
count.update(n => n + 1);
// 方式 3: 在组件中直接赋值 (使用 $ 前缀)
$count = 5;
$count += 1;派生 Store
typescript
import { derived } from 'svelte/store';
import { count } from './stores';
// 创建派生 store
export const doubled = derived(count, $count => $count * 2);
// 从多个 stores 派生
export const sum = derived(
[store1, store2],
([$store1, $store2]) => $store1 + $store2
);最佳实践
- 保持 Store 简单: 每个 store 应该只管理一个关注点
- 使用 TypeScript: 为 store 定义明确的类型
- 避免嵌套订阅: 使用 derived stores 而不是在订阅中再订阅
- 命名约定: 使用描述性的名称,如
recordingStatus而不是status - 初始化: 为 stores 提供合理的初始值
示例
创建一个录制状态 Store
typescript
import { writable, derived } from 'svelte/store';
interface Recording {
id: string;
roomId: string;
status: 'recording' | 'stopped' | 'error';
progress: number;
}
// 创建 writable store
export const recordings = writable<Recording[]>([]);
// 创建派生 store - 正在录制的数量
export const activeRecordingsCount = derived(
recordings,
$recordings => $recordings.filter(r => r.status === 'recording').length
);
// 辅助函数
export function addRecording(recording: Recording) {
recordings.update(list => [...list, recording]);
}
export function updateRecordingProgress(id: string, progress: number) {
recordings.update(list =>
list.map(r => r.id === id ? { ...r, progress } : r)
);
}
export function removeRecording(id: string) {
recordings.update(list => list.filter(r => r.id !== id));
}在组件中使用
svelte
<script lang="ts">
import { recordings, activeRecordingsCount } from '$lib/stores/recordings';
import { onMount } from 'svelte';
onMount(() => {
// 组件挂载时的初始化逻辑
});
</script>
<div>
<h2>活跃录制: {$activeRecordingsCount}</h2>
{#each $recordings as recording}
<div class="recording-item">
<span>{recording.roomId}</span>
<span>{recording.status}</span>
<progress value={recording.progress} max="100"></progress>
</div>
{/each}
</div>与 Tauri 集成
Stores 通常与 Tauri 命令配合使用,从后端获取数据并更新前端状态:
typescript
import { writable } from 'svelte/store';
import { invoke } from '@tauri-apps/api/core';
export const rooms = writable([]);
// 从后端加载数据
export async function loadRooms() {
try {
const data = await invoke('get_rooms');
rooms.set(data);
} catch (error) {
console.error('Failed to load rooms:', error);
}
}
// 监听后端事件
import { listen } from '@tauri-apps/api/event';
listen('room-status-changed', (event) => {
rooms.update(list => {
// 更新对应房间的状态
return list.map(room =>
room.id === event.payload.id
? { ...room, status: event.payload.status }
: room
);
});
});调试
使用 Svelte DevTools 可以查看和调试 stores 的状态变化。
在开发模式下,可以在浏览器控制台中访问 stores:
typescript
// 在开发环境中暴露 stores 用于调试
if (import.meta.env.DEV) {
window.__stores__ = {
recordings,
rooms,
// ... 其他 stores
};
}