You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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代码存在两个致命问题:

  1. 直接修改数组但未通知Store更新:Svelte的writable Store不会自动监听数组的push/shift等原生操作,必须通过Store提供的set或update方法修改值,否则页面无法感知数据变化。
  2. 异步请求未等待完成就修改数组: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);
}

关键修复点说明

  1. 添加set和update方法:从writable中解构出这两个方法,用于主动通知Store更新数据。
  2. 修改add方法:通过update创建新数组,而不是直接修改原始数组,确保Store能感知到变化。
  3. 等待异步请求完成:用Promise.all等待所有API请求结束后再更新Store,避免数据未就绪就修改数组。
  4. 简化数组初始化:getMeal中直接初始化空的ingredients数组,去掉不必要的shift操作。
  5. 替换then嵌套:改用async/await语法让异步代码更易读,减少出错概率。

为什么修改主页面会临时恢复?

修改+page.svelte会触发Vite的热更新,此时页面会重新订阅Store,而这时之前的API请求已经完成,所以能拿到填充好的数据——这只是热更新带来的巧合,不是根本解决方案。


内容的提问来源于stack exchange,提问作者Throwaway_215738

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 18:55:56