如何让 useEffect 按预期工作?

How do I get useEffect to work as expected?

我不熟悉如何使用 spoonacular api 来使用端点获取食谱。这是数据示例。

{
vegetarian: true,
vegan: false,
preparationMinutes: 10,
extendedIngredients: [
     {original: "1/8 teaspoon ground cardamom"},
     {original: "2 to 4 black peppercorns, crushed"},
     {original: "1/4 cup sweetened condensed milk or 4 teaspoons sugar"},
     {original: "1/4 cup loose Darjeeling tea leaves or 5 tea bags black tea"},
     {original: "2 cups water"}
],
id: 165381,
title: "Chai",
readyInMinutes: 10,
servings: 4,
}

出于某种原因,每当我使用 'extendedIngredients' 时都会收到错误消息 'Cannot use properties of undefined',但如果我评论 'extendedIngredients',请保存并再次取消评论并 运行 应用程序, 有效

代码:

const Single=({id})=>{
    
    const [recipe, setRecipe]=useState({})
    
    useEffect(()=>{axios.get(`https://api.spoonacular.com/recipes/${id}/information?apiKey=${apiKey}`)
        .then(res=>{
            setRecipe(res.data)
            console.log("recipe",recipe)
        })
        .catch(err=>{
            console.log(err)
        })
    },[])
 console.log("recipe",recipe)
    return(
      <div>
        
        <Row>
          <Col>
          <h1>{recipe.title}</h1>
          <p>{recipe.extendedIngredients.length} Ingredients</p>
          <p>{recipe.readyInMinutes} Minutes</p>
          <p>{recipe.servings} Servings</p>
          
          </Col>
          <Col>
          <img src={recipe.image} alt={recipe.title}></img>
          </Col>
        </Row>
        <h2>Ingredients</h2>
        {recipe.extendedIngredients.map((ingredient,i)=><p key={i}>{ingredient.original}</p>)}
        <h2>Directions</h2>
        <div dangerouslySetInnerHTML={{__html: recipe.instructions}}/>

      </div>
    )
 
}

第一次渲染收据是一个空对象,所以添加加载状态或类似的东西直到你得到响应

编辑:最好将初始值设置为 null

您可以使用可选链接的新功能(没有 Internet Explorer)。

MDN Optional Chaining

recipe?.extendedIngredients

注意食谱后面的问号。这意味着如果配方未定义,它将退出并且不会尝试读取未定义的属性。

因为您的对象在从 API 获取之前是未定义的。您可以换行条件:


{recipe && <>
            <h1>{recipe.title}</h1>
            <p>{recipe.extendedIngredients.length} Ingredients</p>
            <p>{recipe.readyInMinutes} Minutes</p>
            <p>{recipe.servings} Servings</p>
         </>}        

在加载之前不会尝试渲染配方...

或者您可以在那里设置一些“加载程序”逻辑:

const Single=({id})=>{

const [loading, setLoading] = useState(false)
const [recipe, setRecipe]=useState({})

useEffect(()=>{
setLoading(true);
axios.get(`https://api.spoonacular.com/recipes/${id}/information?apiKey=${apiKey}`)
    .then(res=>{
        setRecipe(res.data)
        console.log("recipe",recipe)
    })
    .catch(err=>{
        console.log(err)
    }).finally(()=>setLoading(false));
},[])

然后 return 如果没有加载东西则加载

if(loading)return(<>Loading...</>);


  return(<div>
    
    <Row>
      <Col>
      <h1>{recipe.title}</h1>
      <p>{recipe.extendedIngredients.length} Ingredients</p>
      <p>{recipe.readyInMinutes} Minutes</p>
      <p>{recipe.servings} Servings</p>
      
      </Col>
      <Col>
      <img src={recipe.image} alt={recipe.title}></img>
      </Col>
    </Row>
    <h2>Ingredients</h2>
    {recipe.extendedIngredients.map((ingredient,i)=><p key={i}>{ingredient.original}</p>)}
    <h2>Directions</h2>
    <div dangerouslySetInnerHTML={{__html: recipe.instructions}}/>

  </div>
)

}

如果你看一下你的 useEffect,你提供了一个空的 dependencies 数组作为参数。这将导致回调(您的获取逻辑)在第一次渲染时触发。 Axios 将创建一个 promise,它可能会很快解决,但对于第一次渲染来说还不够快。这意味着,您需要处理网络请求未返回数据的情况。实现此目的的最简单方法是使用如下条件渲染。

使用此设置,在您的承诺解决之前不会呈现任何内容,一旦它完成,React 将按预期呈现您的组件。下一步是使用微调器或某种正在加载数据的指示器。

const Single = ({ id }) => {
    
  const [recipe, setRecipe] = useState({})
    
  useEffect(() => {
    axios.get(`https://api.spoonacular.com/recipes/${id}/information?apiKey=${apiKey}`)
    .then(res => {
      setRecipe(res.data)
      console.log("recipe", recipe)
    })
    .catch(err => {
      console.log(err)
    })
  }, [])
  console.log("recipe", recipe)
  return (
    <div>
      {recipe && (
        <>
          <Row>
            <Col>
              <h1>{recipe.title}</h1>
              <p>{recipe.extendedIngredients.length} Ingredients</p>
              <p>{recipe.readyInMinutes} Minutes</p>
              <p>{recipe.servings} Servings</p>
            </Col>
            <Col>
              <img src={recipe.image} alt={recipe.title}></img>
            </Col>
          </Row>
          <h2>Ingredients</h2>
          {recipe.extendedIngredients.map((ingredient, i) => (
            <p key={i}>{ingredient.original}</p>
          ))}
          <h2>Directions</h2>
          <div dangerouslySetInnerHTML={{ __html: recipe.instructions }} />
        </>
      )}
    </div>
  )
}