为什么我的 ReactJs 应用程序没有正确更新

Why isn't my ReactJs App not updating correctly

我正在编写一个食谱应用程序。 食谱的食谱存储在 yaml 文件中,这些文件以静态方式存储。当我加载该站点时,它会自动访问一个 index.json 文件,其中所有食谱都已编入索引,并一个接一个地加载它们并将它们添加到一个数组中。然后将该数组提供给 setRecipe 方法,它应该相应地更新 dom。 这不会发生。 我已经尝试 console.log 了一点,在执行此操作时,我记录了预期的数据,但一旦我刷新页面,情况就不再如此了。正在完成对 yaml 文件的请求。为什么会这样?

完整源代码

import React, { useState, useEffect } from 'react';
import jsyaml from 'js-yaml'
import { ListGroup, ListGroupItem } from 'react-bootstrap';

const basePath = '/k0chbuch'
const recipeStore = basePath + '/recipe-store'
const index = recipeStore + '/index.json'

const RecipeList = () => {
    const [recipes, setRecipes] = useState([]);
    useEffect(() => {
        fetchAllRecipes();
    }, []);
    const fetchAllRecipes = () => {
        fetch(index)
            .then(response => {
                if (!response.ok) {
                    throw new Error("HTTP error " + response.status);
                }
                return response.json()
            })
            .then(recipeIndex => {
                let store = []
                recipeIndex.forEach(element => {
                    fetch(recipeStore + '/' + element + '.yaml')
                        .then(res => res.blob())
                        .then(blob => blob.text())
                        .then(text => jsyaml.load(text))
                        .then(recipeObject => {
                            store.push(recipeObject)
                        })
                    
                })
                return store
            })
            .then((all) => setRecipes(all));
    }
    return (
        <div>
            <h1>Rezepte:</h1>
            <ListGroup>
                {recipes.map((r)=>(<ListGroupItem>{r.Titel}</ListGroupItem>))}
            </ListGroup>
        </div>
    );
};
export default RecipeList;

简单的 Yaml 示例:

---
Titel: Hackbraten
Autor: d3v3lop3r
Komplexitaet: 1 
Portionen: 4
Zutaten:
  - 1000g Hack
  - 150g Zwiebeln
  - Gewürze nach Wahl
Zubereitung: |
  Zuerst wird das Hack gewürzt, dann die Zwiebeln braten und dem Hack zugeben. Kräftig kneten, dann bei 200°C eine Stunde backen.

Kommentar: Schmeckt am besten mit Kartoffeln!

useEffect 基于作为 useEffect 挂钩的第二个参数传递的依赖项数组运行。

试试这个,

const [recipes, setRecipes] = useState([]);

useEffect(() => {
  if(!recipes && recipes.length == 0){
    fetchAllRecipes();
  }
}, [recipes])

这将导致您的 useEffect 挂钩在 recipes 状态发生变化时立即调用,但只会导致 fetchRecipes() 函数在 recipes 状态为null或数组长度为0

您的问题在于在第二个 .then() 中填充 store 数组:使用 Array.prototype.forEach 时,请记住 return 并没有真正执行任何操作。在解析 promise 之前,您不需要等待 blob 被解析。事实上,菊花链式承诺的这一部分立即解析 :

.then(recipeIndex => {
    let store = [];
    recipeIndex.forEach(element => {
        fetch(...);
    });

    // `store` is returned immediately without waiting for forEach!
    return store;
})

相反,我建议使用 Array.prototype.map to return an array of promises based off recipeIndex. Then return Promise.all(),它确保所有承诺都得到解决:

fetch(index)
    .then(response => {
        if (!response.ok) {
            throw new Error("HTTP error " + response.status);
        }
        return response.json();
    })
    .then(recipeIndex => {
        const promises = recipeIndex.map((element) => {
            return fetch(recipeStore + '/' + element + '.yaml')
                .then(res => res.blob())
                .then(blob => blob.text())
                .then(text => jsyaml.load(text));
        });
        return Promise.all(promises);
    })
    .then((all) => setRecipes(all));

使用 for 循环怎么样? (可以,但不推荐)

另一种方法是使用 for 循环,然后您可以使用 async/await。但是我通常不建议这样做,原因有二:

  1. for loop + await 意味着每个请求都在彼此之后调度并且 不并行 ,这会增加延迟。上面的 Array.prototype.map 解决方案同时分派获取请求。
  2. 有些 linter 不鼓励在 .then()
  3. 中嵌套异步回调

但是,如果您想尝试一下,for 循环是完全可行的:

fetch(index)
    .then(response => {
        if (!response.ok) {
            throw new Error("HTTP error " + response.status);
        }
        return response.json();
    })
    .then(async (recipeIndex) => {
        const recipes = [];
        for (const element of recipeIndex) {
            const recipe = await fetch(recipeStore + '/' + element + '.yaml')
                .then(res => res.blob())
                .then(blob => blob.text())
                .then(text => jsyaml.load(text));
            recipes.push(recipe);
        }
    })
    .then((all) => setRecipes(all));