Svelte 状态管理进阶:如何组合多个 store 实现复杂逻辑
Svelte 状态管理进阶:如何组合多个 store 实现复杂逻辑
Svelte 中 store 的基础概念回顾
在深入探讨如何组合多个 store 之前,我们先来简单回顾一下 Svelte 中 store 的基本概念。在 Svelte 里,store 是一种管理应用状态的方式。它是一个具有 subscribe
方法的对象,该方法允许我们监听状态的变化。
import { writable } from'svelte/store';
// 创建一个简单的 writable store
const count = writable(0);
// 订阅状态变化
const unsubscribe = count.subscribe((value) => {
console.log(`The count has changed to: ${value}`);
});
// 更新状态
count.set(1);
// 取消订阅
unsubscribe();
上述代码通过 writable
函数创建了一个名为 count
的 store,初始值为 0。我们可以通过 subscribe
方法注册一个回调函数,每当 count
的值发生变化时,这个回调函数就会被调用。set
方法用于更新 store 的值。当我们不再需要监听状态变化时,可以调用 unsubscribe
函数取消订阅。
简单组合多个 store 的场景
- 加法运算场景
假设我们有两个
writable
store,分别表示两个数字,我们希望得到它们的和。
<script>
import { writable } from'svelte/store';
const num1 = writable(5);
const num2 = writable(3);
// 创建一个新的 store 来表示它们的和
const sum = derived([num1, num2], ([$num1, $num2]) => {
return $num1 + $num2;
});
let unsubscribeSum;
// 订阅 sum 的变化
unsubscribeSum = sum.subscribe((value) => {
console.log(`The sum is: ${value}`);
});
// 更新 num1 和 num2
num1.set(7);
num2.set(4);
</script>
在这段代码中,我们使用 derived
函数来创建一个新的 store sum
。derived
函数接受两个参数,第一个参数是一个包含依赖 store(这里是 num1
和 num2
)的数组,第二个参数是一个回调函数。当任何依赖 store 的值发生变化时,这个回调函数就会被调用,回调函数的参数是依赖 store 当前的值(这里用 $num1
和 $num2
表示),回调函数的返回值会作为新 store sum
的值。
- 逻辑判断场景 假设有两个布尔类型的 store,我们希望根据这两个 store 的值来判断一个复杂的逻辑条件。
<script>
import { writable } from'svelte/store';
const isLoggedIn = writable(false);
const hasPermission = writable(false);
const canAccess = derived([isLoggedIn, hasPermission], ([$isLoggedIn, $hasPermission]) => {
return $isLoggedIn && $hasPermission;
});
let unsubscribeAccess;
unsubscribeAccess = canAccess.subscribe((value) => {
if (value) {
console.log('User can access the resource.');
} else {
console.log('User cannot access the resource.');
}
});
// 更新状态
isLoggedIn.set(true);
hasPermission.set(true);
</script>
这里我们通过 derived
创建了 canAccess
store,它的值取决于 isLoggedIn
和 hasPermission
。只有当用户登录(isLoggedIn
为 true
)并且有权限(hasPermission
为 true
)时,canAccess
才为 true
。
嵌套组合 store
- 多层依赖组合
考虑一个稍微复杂的场景,我们有三个 store:
baseValue
,multiplier
和adder
。我们希望通过baseValue
乘以multiplier
再加上adder
得到最终结果。
<script>
import { writable, derived } from'svelte/store';
const baseValue = writable(2);
const multiplier = writable(3);
const adder = writable(4);
const intermediate = derived([baseValue, multiplier], ([$baseValue, $multiplier]) => {
return $baseValue * $multiplier;
});
const finalResult = derived([intermediate, adder], ([$intermediate, $adder]) => {
return $intermediate + $adder;
});
let unsubscribeFinal;
unsubscribeFinal = finalResult.subscribe((value) => {
console.log(`The final result is: ${value}`);
});
// 更新状态
baseValue.set(3);
multiplier.set(4);
adder.set(5);
</script>
在这个例子中,我们首先通过 derived
创建了 intermediate
store,它依赖于 baseValue
和 multiplier
。然后,我们又基于 intermediate
和 adder
创建了 finalResult
store。这样就形成了一个嵌套的依赖关系,任何底层 store 的变化都会影响到最终的 finalResult
。
- 动态嵌套组合
有时候,我们可能需要根据某些条件动态地组合 store。比如,我们有一个开关
isAdvancedCalculation
,当它为true
时,我们希望使用更复杂的计算逻辑(结合更多的 store),当它为false
时,使用简单的计算逻辑。
<script>
import { writable, derived } from'svelte/store';
const baseValue = writable(2);
const simpleMultiplier = writable(3);
const complexMultiplier = writable(5);
const adder = writable(4);
const isAdvancedCalculation = writable(false);
const simpleCalculation = derived([baseValue, simpleMultiplier, adder], ([$baseValue, $simpleMultiplier, $adder]) => {
return $baseValue * $simpleMultiplier + $adder;
});
const complexCalculation = derived([baseValue, complexMultiplier, adder], ([$baseValue, $complexMultiplier, $adder]) => {
return ($baseValue * $complexMultiplier) * 2 + $adder;
});
const finalCalculation = derived([isAdvancedCalculation, simpleCalculation, complexCalculation], ([$isAdvancedCalculation, $simpleCalculation, $complexCalculation]) => {
return $isAdvancedCalculation? $complexCalculation : $simpleCalculation;
});
let unsubscribeFinal;
unsubscribeFinal = finalCalculation.subscribe((value) => {
console.log(`The final calculation result is: ${value}`);
});
// 更新状态
isAdvancedCalculation.set(true);
baseValue.set(3);
</script>
在这个示例中,我们创建了 simpleCalculation
和 complexCalculation
两个不同的计算结果 store,然后通过 finalCalculation
根据 isAdvancedCalculation
的值来决定使用哪一个结果。
处理异步操作的 store 组合
- 基于异步数据的计算
假设我们有一个 API 调用返回的数据 store
userData
,并且有一个本地配置 storedisplayOption
。我们希望根据displayOption
对userData
进行一些处理,例如格式化显示。
<script>
import { writable, derived } from'svelte/store';
import { onMount } from'svelte';
const userData = writable(null);
const displayOption = writable('default');
onMount(() => {
// 模拟 API 调用
setTimeout(() => {
userData.set({ name: 'John', age: 30 });
}, 1000);
});
const formattedUserData = derived([userData, displayOption], ([$userData, $displayOption]) => {
if (!$userData) return '';
if ($displayOption === 'default') {
return `Name: ${$userData.name}, Age: ${$userData.age}`;
} else if ($displayOption === 'compact') {
return `${$userData.name} (${$userData.age})`;
}
return '';
});
let unsubscribeFormatted;
unsubscribeFormatted = formattedUserData.subscribe((value) => {
console.log(`Formatted user data: ${value}`);
});
// 更新显示选项
displayOption.set('compact');
</script>
在这段代码中,userData
是通过模拟的异步操作(setTimeout
模拟 API 调用)来更新的。formattedUserData
依赖于 userData
和 displayOption
,当 userData
有值且 displayOption
变化时,会重新计算格式化后的数据。
- 多个异步 store 的组合 有时候我们可能有多个异步 API 调用,并且需要将这些异步返回的数据进行组合。比如,我们有一个获取用户信息的 API 和一个获取用户偏好设置的 API。
<script>
import { writable, derived } from'svelte/store';
import { onMount } from'svelte';
const userInfo = writable(null);
const userPreferences = writable(null);
onMount(() => {
// 模拟获取用户信息的 API 调用
setTimeout(() => {
userInfo.set({ name: 'Jane', age: 25 });
}, 1000);
// 模拟获取用户偏好设置的 API 调用
setTimeout(() => {
userPreferences.set({ theme: 'dark', language: 'en' });
}, 1500);
});
const combinedUserInfo = derived([userInfo, userPreferences], ([$userInfo, $userPreferences]) => {
if (!$userInfo ||!$userPreferences) return null;
return {
...$userInfo,
...$userPreferences
};
});
let unsubscribeCombined;
unsubscribeCombined = combinedUserInfo.subscribe((value) => {
if (value) {
console.log(`Combined user info: ${JSON.stringify(value)}`);
}
});
</script>
在这个例子中,userInfo
和 userPreferences
都是通过异步操作更新的 store。combinedUserInfo
使用 derived
来组合这两个 store 的数据,只有当两个 store 都有值时,才会生成组合后的信息。
使用 store 组合实现复杂业务逻辑
- 购物车场景
在一个电商应用的购物车模块中,我们可能有多个相关的 store。例如,
cartItems
store 表示购物车中的商品列表,coupon
store 表示用户使用的优惠券,shippingOption
store 表示用户选择的配送方式。我们需要根据这些 store 来计算订单总价。
<script>
import { writable, derived } from'svelte/store';
const cartItems = writable([
{ id: 1, name: 'Product 1', price: 10, quantity: 2 },
{ id: 2, name: 'Product 2', price: 15, quantity: 1 }
]);
const coupon = writable(null);
const shippingOption = writable({ cost: 5 });
const subTotal = derived(cartItems, ($cartItems) => {
return $cartItems.reduce((total, item) => {
return total + item.price * item.quantity;
}, 0);
});
const discount = derived(coupon, ($coupon) => {
if (!$coupon) return 0;
return $coupon.amount;
});
const total = derived([subTotal, discount, shippingOption], ([$subTotal, $discount, $shippingOption]) => {
return $subTotal - $discount + $shippingOption.cost;
});
let unsubscribeTotal;
unsubscribeTotal = total.subscribe((value) => {
console.log(`The total price of the order is: ${value}`);
});
// 更新购物车商品数量
cartItems.update((items) => {
items[0].quantity = 3;
return items;
});
// 使用优惠券
coupon.set({ amount: 3 });
</script>
在这个购物车场景中,我们首先通过 cartItems
计算出 subTotal
。然后根据 coupon
计算出 discount
。最后,基于 subTotal
、discount
和 shippingOption
计算出最终的 total
。这样通过组合多个 store,实现了购物车复杂的价格计算逻辑。
- 任务管理场景
在一个任务管理应用中,我们可能有
tasks
store 表示所有任务列表,filter
store 表示用户选择的任务过滤条件(如只显示已完成任务或未完成任务),sortOption
store 表示用户选择的排序方式(如按截止日期排序或按优先级排序)。
<script>
import { writable, derived } from'svelte/store';
const tasks = writable([
{ id: 1, title: 'Task 1', completed: false, dueDate: '2023 - 10 - 10', priority: 'high' },
{ id: 2, title: 'Task 2', completed: true, dueDate: '2023 - 10 - 15', priority: 'low' }
]);
const filter = writable('all');
const sortOption = writable('dueDate');
const filteredTasks = derived([tasks, filter], ([$tasks, $filter]) => {
if ($filter === 'all') {
return $tasks;
} else if ($filter === 'completed') {
return $tasks.filter(task => task.completed);
} else if ($filter === 'incomplete') {
return $tasks.filter(task =>!task.completed);
}
return [];
});
const sortedTasks = derived([filteredTasks, sortOption], ([$filteredTasks, $sortOption]) => {
if ($sortOption === 'dueDate') {
return $filteredTasks.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
} else if ($sortOption === 'priority') {
const priorities = { high: 0, medium: 1, low: 2 };
return $filteredTasks.sort((a, b) => priorities[a.priority] - priorities[b.priority]);
}
return $filteredTasks;
});
let unsubscribeSorted;
unsubscribeSorted = sortedTasks.subscribe((value) => {
console.log(`Sorted and filtered tasks: ${JSON.stringify(value)}`);
});
// 更新过滤条件
filter.set('completed');
// 更新排序选项
sortOption.set('priority');
</script>
在这个任务管理场景中,我们首先根据 filter
对 tasks
进行过滤得到 filteredTasks
,然后再根据 sortOption
对 filteredTasks
进行排序得到 sortedTasks
。通过这种方式,我们能够根据用户的不同操作动态地组合和处理任务数据,以满足复杂的业务需求。
优化组合 store 的性能
- 减少不必要的计算
在使用
derived
组合多个 store 时,要注意避免不必要的计算。例如,如果一个derived
store 的依赖 store 变化频繁,但某些依赖实际上对计算结果没有影响,我们可以通过更精细的逻辑来避免不必要的重新计算。
<script>
import { writable, derived } from'svelte/store';
const counter = writable(0);
const userData = writable({ name: 'Alice', age: 30 });
const userInfo = derived(userData, ($userData) => {
return `Name: ${$userData.name}, Age: ${$userData.age}`;
});
const combinedInfo = derived([counter, userInfo], ([$counter, $userInfo]) => {
// 这里 counter 实际上对 userInfo 的生成没有影响
// 可以将 userInfo 的计算逻辑提取出来,避免每次 counter 变化时都重新计算 userInfo
return {
counter: $counter,
userInfo: $userInfo
};
});
let unsubscribeCombined;
unsubscribeCombined = combinedInfo.subscribe((value) => {
console.log(`Combined info: ${JSON.stringify(value)}`);
});
// 更新 counter
counter.set(1);
</script>
在这个例子中,userInfo
的计算只依赖于 userData
,而 combinedInfo
虽然依赖于 counter
和 userInfo
,但 counter
的变化不影响 userInfo
的生成。因此,我们可以在 combinedInfo
的计算中,避免每次 counter
变化时都重新计算 userInfo
。
- 缓存计算结果
对于一些计算代价较高的
derived
store,我们可以考虑缓存计算结果。例如,假设我们有一个复杂的计算逻辑,需要对一个大数组进行多次过滤和排序。
<script>
import { writable, derived } from'svelte/store';
const largeArray = writable([...Array(1000).keys()]);
const filterValue = writable(500);
const sortOrder = writable('asc');
let cachedResult;
const processedArray = derived([largeArray, filterValue, sortOrder], ([$largeArray, $filterValue, $sortOrder]) => {
if (cachedResult && cachedResult.filterValue === $filterValue && cachedResult.sortOrder === $sortOrder) {
return cachedResult.array;
}
let filteredArray = $largeArray.filter(num => num > $filterValue);
if ($sortOrder === 'asc') {
filteredArray.sort((a, b) => a - b);
} else {
filteredArray.sort((a, b) => b - a);
}
cachedResult = {
filterValue: $filterValue,
sortOrder: $sortOrder,
array: filteredArray
};
return filteredArray;
});
let unsubscribeProcessed;
unsubscribeProcessed = processedArray.subscribe((value) => {
console.log(`Processed array: ${JSON.stringify(value)}`);
});
// 更新过滤值
filterValue.set(400);
</script>
在这个示例中,我们通过 cachedResult
来缓存 processedArray
的计算结果。当 filterValue
或 sortOrder
没有变化时,直接返回缓存的结果,避免了重复的高代价计算。
错误处理与健壮性
- 处理 store 初始值为 null 或 undefined 的情况
在组合多个 store 时,很可能会遇到某些 store 初始值为
null
或undefined
的情况。例如,在异步获取数据的场景下,数据可能还未加载完成。
<script>
import { writable, derived } from'svelte/store';
import { onMount } from'svelte';
const userData = writable(null);
const settings = writable(null);
onMount(() => {
// 模拟异步获取用户数据
setTimeout(() => {
userData.set({ name: 'Bob', age: 28 });
}, 1000);
// 模拟异步获取设置
setTimeout(() => {
settings.set({ theme: 'light' });
}, 1500);
});
const combinedData = derived([userData, settings], ([$userData, $settings]) => {
if (!$userData ||!$settings) return null;
return {
...$userData,
...$settings
};
});
let unsubscribeCombined;
unsubscribeCombined = combinedData.subscribe((value) => {
if (value) {
console.log(`Combined data: ${JSON.stringify(value)}`);
} else {
console.log('Data is not ready yet.');
}
});
</script>
在这段代码中,userData
和 settings
初始值都为 null
。在 combinedData
的计算中,我们首先检查这两个 store 是否都有值,如果有一个为 null
,则返回 null
,以避免在数据不完整时出现错误。
- 处理异步操作中的错误 当异步操作(如 API 调用)出现错误时,我们需要妥善处理。例如,在获取用户数据的 API 调用失败时,我们可以更新 store 来表示错误状态。
<script>
import { writable, derived } from'svelte/store';
import { onMount } from'svelte';
const userData = writable(null);
const userDataError = writable(null);
onMount(() => {
// 模拟 API 调用失败
setTimeout(() => {
const success = false;
if (success) {
userData.set({ name: 'Charlie', age: 32 });
} else {
userDataError.set('Failed to fetch user data');
}
}, 1000);
});
const userInfo = derived([userData, userDataError], ([$userData, $userDataError]) => {
if ($userDataError) {
return `Error: ${$userDataError}`;
} else if ($userData) {
return `Name: ${$userData.name}, Age: ${$userData.age}`;
}
return 'Loading...';
});
let unsubscribeUserInfo;
unsubscribeUserInfo = userInfo.subscribe((value) => {
console.log(`User info: ${value}`);
});
</script>
在这个例子中,我们创建了 userDataError
store 来表示获取用户数据时的错误。在 userInfo
的计算中,根据 userDataError
和 userData
的状态返回不同的信息,从而提高了应用的健壮性。
总结多个 store 组合的最佳实践
-
清晰的依赖关系 在组合多个 store 时,要确保依赖关系清晰。每个
derived
store 的依赖应该明确,并且尽量避免创建过于复杂或难以理解的依赖链。这样有助于代码的维护和调试。例如,在购物车场景中,subTotal
、discount
和shippingOption
与total
的依赖关系一目了然,易于理解和修改。 -
命名规范 为 store 和相关的计算逻辑使用清晰、有意义的命名。比如在任务管理场景中,
tasks
、filter
、sortOption
、filteredTasks
和sortedTasks
这些命名能够准确地描述其功能,使得代码的可读性大大提高。 -
性能优化意识 时刻关注性能问题,通过减少不必要的计算和缓存计算结果等方式来优化组合 store 的性能。如在处理大数组的计算场景中,通过缓存结果避免了重复的高代价操作。
-
错误处理 要充分考虑各种可能出现的错误情况,如 store 初始值为
null
或undefined
,以及异步操作中的错误等。通过合理的错误处理机制,提高应用的健壮性,如在异步获取数据场景中对错误状态的处理。
通过遵循这些最佳实践,我们能够更有效地使用 Svelte 的 store 组合来实现复杂的逻辑,打造出高效、健壮且易于维护的前端应用。