简体   繁体   English

反应钩子和 fetch() 建议

[英]React hooks and fetch() advice

I have a component that fetches a random person, saves it to a state variable array and after mapping through that array, it returns certain elements.我有一个组件可以获取一个随机人,将其保存到 state 变量数组中,并在映射到该数组后返回某些元素。 My goal is to render a "social card" with an avatar and personal infos, I want to use a material UI CARD component for this.我的目标是使用头像和个人信息呈现“社交卡”,我想为此使用材质 UI CARD 组件。 Also I'd like to get any feedback on my logic here, whether this is a good way to achieve what I have achieved.另外,我想在这里获得有关我的逻辑的任何反馈,这是否是实现我所取得的成就的好方法。 The setTimeout() is only there so I can render some loading animation. setTimeout() 只在那里,所以我可以渲染一些加载 animation。 Also when I tried to return a full card component inside the array map, I could not render {item.something.something}此外,当我尝试返回数组 map 内的完整卡组件时,我无法渲染 {item.something.something}

export default function SocialCardsfunction() {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);

  const classes = useStyles();

  const nextPerson = () => {
    fetch("https://randomuser.me/api")
      .then((response) => response.json())
      .then((response) => {
        setItems(response.results);
      });
  };
  
  useEffect(() => {
    fetch("https://randomuser.me/api")
      .then((response) => response.json())
      .then((response) => {
        setTimeout(() => {
          setItems(response.results);
          setLoading(false);
        }, 1000);
      });
  }, []);

  if (loading) {
    return <div>Loading ...</div>;
  } else {
    return (
      <div>
        {items.map((item, i) => {
          return (
            <div>
              <h2>{item.name.first}</h2>
              <img src={item.picture.large} alt='picture' key={i} />
              <button onClick={() => nextPerson()}>click me</button>
            </div>
          );
        })}
      </div>
    );
  }
}

const useStyles = makeStyles({
  root: {
    minWidth: 275,
  },
  bullet: {
    display: "inline-block",
    margin: "0 2px",
    transform: "scale(0.8)",
  },
  title: {
    fontSize: 14,
  },
  pos: {
    marginBottom: 12,
  },
});

Changes Made所做的更改

  1. Instead of repeating the same code make a function and call that function in useEffect .不要重复相同的代码,而是创建 function 并在 useEffect 中调用useEffect
  2. Each child in a list should have a unique " key " prop.列表中的每个孩子都应该有一个唯一的“ key ”道具。
  3. Card from Material UI is used.(I have not focused on styling much XD)使用 Material UI 中的卡片。(我没有过多关注样式 XD)
import {
  Button,
  Card,
  CardActions,
  makeStyles,
} from "@material-ui/core";
import React, { useEffect, useState } from "react";

export default function SocialCardsfunction() {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);

  const classes = useStyles();

  const fetchPerson = () => {
    fetch("https://randomuser.me/api")
      .then((response) => response.json())
      .then((response) => {
        setTimeout(() => {
          setItems(response.results);
          setLoading(false);
        }, 1000);
      });
  };

  useEffect(() => {
    fetchPerson();
  }, []);

  if (loading) {
    return <div>Loading ...</div>;
  } else {
    return (
      <div>
        {items.map((item, i) => {
          return (
            <div key={i}>
              <Card className={classes.root}>
              <h2>{item.name.first}</h2>
                <img
                  alt="img"
                  src={item.picture.large}
                  className={classes.large}
                />
                <CardActions>
                  <Button onClick={() => fetchPerson()} size="small">Next</Button>
                </CardActions>
              </Card>
           
            </div>
          );
        })}
      </div>
    );
  }
}

const useStyles = makeStyles({
  root: {
    minWidth: 275
  },
  bullet: {
    display: "inline-block",
    margin: "0 2px",
    transform: "scale(0.8)"
  },
  title: {
    fontSize: 14
  },
  pos: {
    marginBottom: 12
  }
});

Your logic looks good but there's always something that can be improved a little further.您的逻辑看起来不错,但总有一些可以进一步改进的地方。

If you would like to store only a single user a time in your SocialCard then I would extract only one single user from the API rather than a list because the API returns an array of only one object anyway.如果您只想在SocialCard中一次存储一个用户,那么我将只从 API 而不是列表中提取一个用户,因为 API 无论如何都会返回一个仅包含一个 ZA8CFDE6331BD59EB2AC96F8911C4B666 的数组。

First, I would change the state and include status and error .首先,我将更改 state 并包括statuserror With status , you can easily check in which status your component is at the moment and based on that render different things/messages in your App.使用status ,您可以轻松检查您的组件当前处于哪个状态,并根据该状态在您的应用程序中呈现不同的事物/消息。 With error , you can define your own error in case something goes wrong and then render an error message in your App.使用error ,您可以定义自己的错误以防出现问题,然后在您的应用程序中呈现错误消息。 Also, I've re-used your fetch as it was used twice and it was redundant.另外,我重新使用了您的 fetch,因为它被使用了两次,而且是多余的。 This way you have a nice single function that can be used anywhere while also making sure that loading is shown while it fetches the result.这样你就有了一个很好的单个 function 可以在任何地方使用,同时还确保在获取结果时显示loading I've also used MaterialUI Card component for rendering the user data.我还使用 MaterialUI Card组件来呈现用户数据。 You can check how the result looks like here您可以在此处查看结果的样子

import React, { useState, useEffect } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Card from "@material-ui/core/Card";
import CardActionArea from "@material-ui/core/CardActionArea";
import CardActions from "@material-ui/core/CardActions";
import CardContent from "@material-ui/core/CardContent";
import CardMedia from "@material-ui/core/CardMedia";
import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";

const useStyles = makeStyles({
  root: {
    maxWidth: 345
  }
});

function App() {
  const classes = useStyles();
  const [state, setState] = useState({
    user: {},
    status: "idle",
    error: null
  });
  const { user, status, error } = state;

  const getUser = () => {
    setState((prevState) => ({
      ...prevState,
      status: "loading"
    }));

    fetch("https://randomuser.me/api").then(async (res) => {
      if (res.ok) {
        const data = await res.json();

        setState((prevState) => ({
          ...prevState,
          user: data.results[0],
          status: "processed"
        }));
      } else {
        setState({
          user: {},
          status: "failed",
          error: "Error message"
        });
      }
    });
  };

  useEffect(() => {
    getUser();
  }, []);

  if (status === "loading") {
    return <div>Loading ...</div>;
  }
  if (status === "failed") {
    return <div>{error}</div>;
  }
  if (status === "processed") {
    return (
      <Card className={classes.root}>
        <CardActionArea>
          <CardMedia
            component="img"
            alt="user"
            height="140"
            image={user.picture.large}
            title="user"
          />
          <CardContent>
            <Typography gutterBottom variant="h5" component="h2">
              {user.name.first}
            </Typography>
          </CardContent>
        </CardActionArea>
        <CardActions>
          <Button size="small" color="primary" onClick={getUser}>
            Show another user
          </Button>
        </CardActions>
      </Card>
    );
  } else {
    // Default placeholder
    return <div>hi</div>;
  }
}

export default App;

However, if you would like to store all the users you fetch when clicking the button, I would suggest moving the fetch and state of users into the parent component and leave SocialCard component only for rendering a single user.但是,如果您想存储单击按钮时获取的所有用户,我建议将用户的 fetch 和 state 移动到父组件中,并保留SocialCard组件仅用于呈现单个用户。 Then, in the parent component I would ensure that the setState would look something like this in the getUser function然后,在父组件中,我将确保setStategetUser function 中看起来像这样

setState((prevState) => ({
  ...prevState,
  users: [...prevState.users, ...data.results], // This merges your previous user objects with new user object
  status: "processed"
}));

This way, you can keep all the users in your parent component and using map you can render each user with your SocialCard component.这样,您可以将所有用户保留在父组件中,并使用map您可以使用SocialCard组件呈现每个用户。 Take a note that you would need to refactor your components further in order to make this work.请注意,您需要进一步重构组件才能使其正常工作。 I'll leave it as an exercise for you if you want to go this route.如果您想使用 go 这条路线,我会将其作为练习留给您。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM