SvelteKit+Vite问题:未修改使用Store的文件时Store响应异常
SvelteKit + Vite 自定义Store数据不更新问题
问题现象
- 自定义Store存储从API获取的数组,主页面
+page.svelte通过onMount()初始化Store并展示数据 - 首次加载数据正常显示,刷新页面或编译保存后,Store显示为空数组(无报错,但页面无数据),但实际API数据已经填充完成
- 只要修改
+page.svelte(哪怕加一条注释),数据就会正常显示;修改子组件或+layout.svelte既不会引发异常,也无法修复异常状态
项目环境
通过npm create svelte@latest创建,选择了TypeScript、ESLint、Prettier,额外添加了SCSS,当前处于开发模式(npm run dev),无编译错误。
相关代码
+page.svelte
<script lang="ts"> import { onMount } from "svelte"; import { meals } from "../store/meals"; import MealCard from "./MealCard.svelte"; onMount( () => { meals.initialise(); }); </script> <h1>Today's Selection</h1> <div class="list"> {#each $meals as meal} <MealCard meal={meal}/> {/each} </div>
store/meals.ts(简化类型定义后)
import { writable } from "svelte/store"; function createMeals() { const meals = [{ id: "", name: "", preview: "", ingredients: [], tags: [], category: "", origin: "", source: "", video: "", instructions: "", locked: false }]; const nbOfMeals = 3; const {subscribe} = writable(meals); return { subscribe, add: (meal) => { meals.push(meal); }, initialise: async () => { if (meals.length === nbOfMeals) { return; } for (let iMeal = 0; iMeal < nbOfMeals; iMeal++) { getMeal(); } meals.shift(); } } } export const meals = createMeals(); async function getMeal() { const meal = { id: "1", name: "", preview: "", ingredients: [""], tags: [], category: "", origin: "", source: "", video: "", instructions: "", locked: false, }; const url = "https://www.themealdb.com/api/json/v1/1/random.php"; await fetch(url) .then((response) => response.json()) .then((data) => { const _data = data.meals[0]; meal.id = _data.idMeal; meal.name = _data.strMeal; meal.preview = _data.strMealThumb; const tags = _data.strTags; if (tags) { meal.tags = tags.split(","); } meal.category = _data.strCategory; meal.origin = _data.strArea; meal.source = _data.strSource; meal.video = _data.strYoutube; meal.instructions = _data.strInstructions; meal.ingredients = [""]; let iIngr = 1; while (_data["strIngredient" + iIngr]) { let toAdd; const regexp = /\d|¼|½|¾|(\w+ of)/; if (regexp.test(_data["strMeasure" + iIngr])) { toAdd = _data["strMeasure" + iIngr] + " " + _data["strIngredient" + iIngr]; } else if (/[^\w]/.test(_data["strMeasure" + iIngr])) { toAdd = _data["strIngredient" + iIngr]; } else { toAdd = _data["strIngredient" + iIngr] + " (" + _data["strMeasure" + iIngr].toLowerCase() + ")"; } meal.ingredients.push(toAdd); iIngr++; } meal.ingredients.shift(); meals.add(meal); }); }
问题原因与修复方案
核心问题
你的Store代码存在两个致命问题:
- 直接修改数组但未通知Store更新:Svelte的writable Store不会自动监听数组的
push/shift等原生操作,必须通过Store提供的set或update方法修改值,否则页面无法感知数据变化。 - 异步请求未等待完成就修改数组:
initialise方法里循环调用getMeal()但没有加await,导致meals.shift()在API请求返回前就执行了;而且异步请求完成后修改数组,也没有通知Store更新。
修复后的完整代码(store/meals.ts)
import { writable } from "svelte/store"; function createMeals() { const meals = [{ id: "", name: "", preview: "", ingredients: [], tags: [], category: "", origin: "", source: "", video: "", instructions: "", locked: false }]; const nbOfMeals = 3; // 解构出set和update方法,用于更新Store const { subscribe, set, update } = writable(meals); return { subscribe, add: (meal) => { // 用update生成新数组,通知Store更新 update(currentMeals => [...currentMeals, meal]); }, initialise: async () => { if (meals.length === nbOfMeals) { return; } // 等待所有API请求完成,避免提前修改数组 await Promise.all(Array.from({ length: nbOfMeals }, () => getMeal())); // 去掉初始的空对象,用set更新Store set(meals.slice(1)); } } } export const meals = createMeals(); async function getMeal() { const meal = { id: "1", name: "", preview: "", ingredients: [], // 直接初始化空数组,简化后续操作 tags: [], category: "", origin: "", source: "", video: "", instructions: "", locked: false, }; const url = "https://www.themealdb.com/api/json/v1/1/random.php"; // 改用async/await语法,避免嵌套then,更清晰 const response = await fetch(url); const data = await response.json(); const _data = data.meals[0]; meal.id = _data.idMeal; meal.name = _data.strMeal; meal.preview = _data.strMealThumb; const tags = _data.strTags; if (tags) { meal.tags = tags.split(","); } meal.category = _data.strCategory; meal.origin = _data.strArea; meal.source = _data.strSource; meal.video = _data.strYoutube; meal.instructions = _data.strInstructions; let iIngr = 1; while (_data[`strIngredient${iIngr}`]) { let toAdd; const regexp = /\d|¼|½|¾|(\w+ of)/; if (regexp.test(_data[`strMeasure${iIngr}`])) { toAdd = `${_data[`strMeasure${iIngr}`]} ${_data[`strIngredient${iIngr}`]}`; } else if (/[^\w]/.test(_data[`strMeasure${iIngr}`])) { toAdd = _data[`strIngredient${iIngr}`]; } else { toAdd = `${_data[`strIngredient${iIngr}`]} (${_data[`strMeasure${iIngr}`].toLowerCase()})`; } meal.ingredients.push(toAdd); iIngr++; } meals.add(meal); }
关键修复点说明
- 添加
set和update方法:从writable中解构出这两个方法,用于主动通知Store更新数据。 - 修改
add方法:通过update创建新数组,而不是直接修改原始数组,确保Store能感知到变化。 - 等待异步请求完成:用
Promise.all等待所有API请求结束后再更新Store,避免数据未就绪就修改数组。 - 简化数组初始化:
getMeal中直接初始化空的ingredients数组,去掉不必要的shift操作。 - 替换
then嵌套:改用async/await语法让异步代码更易读,减少出错概率。
为什么修改主页面会临时恢复?
修改+page.svelte会触发Vite的热更新,此时页面会重新订阅Store,而这时之前的API请求已经完成,所以能拿到填充好的数据——这只是热更新带来的巧合,不是根本解决方案。
内容的提问来源于stack exchange,提问作者Throwaway_215738
相关产品推荐
相关产品推荐

