React中调用API的最优方式?封装API文件返回数据为undefined如何解决
核心问题
你当前的写法违反了React Hook的使用规则:useState、useEffect这类Hook只能在React函数组件内部,或者以use开头的自定义Hook函数中调用,不能在普通JS对象的方法里使用,这是你无法正常拿到返回值的根本原因。
同时你的API封装逻辑和UI层状态逻辑耦合,不符合分层设计原则,合理的实现应该把纯接口请求逻辑和React状态处理分开。
改造方案
1. 重写API封装层(Movie.js)
API文件只负责封装纯请求逻辑,直接返回Promise,不处理任何React相关状态:
import axios from 'axios'; const apiKey = 'apikey'; const url = 'https://api.themoviedb.org/3'; const Movie = { // 直接返回axios请求的Promise对象 GetMovieTrends() { return axios.get(`${url}/trending/movie/day?api_key=${apiKey}`) .then(response => response.data) .catch(err => { console.log(err); throw err; }); }, GetMovieGenres() { return axios.get(`${url}/genre/movie/list?api_key=${apiKey}`) .then(response => response.data.genres) .catch(err => { console.log(err); throw err; }); } }; export default Movie;
2. 方案一:组件内处理请求状态(适合单组件使用)
直接在业务组件内用Hook处理请求、状态和渲染逻辑:
import React, {useState, useEffect} from "react"; import MovieImage from "./movie/MovieImage"; import MovieTextContainer from "./movie/MovieTextContainer"; import Movie from '../models/Movie.js'; import CircularProgress from '@material-ui/core/CircularProgress'; import { withStyles } from '@material-ui/core/styles'; const WaitForMovie = withStyles(() => ({ root: { color: '#212121', width: '64px !important', height: '64px !important', }, }))(CircularProgress); const TrendContainer = () => { // 声明状态管理电影数据和加载状态 const [movieTrends, setMovieTrends] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const fetchData = async () => { setLoading(true); try { const data = await Movie.GetMovieTrends(); setMovieTrends(data.results || []); } finally { setLoading(false); } }; fetchData(); }, []); return ( <div> <h1 className='heading1 bottom-margin'>Movie Trending</h1> <div className='container-movie'> {loading ? ( <WaitForMovie /> ) : ( movieTrends.slice(0, 7).map((movie) => ( <div className='wrap-card-movie bottom-margin' key={movie.id}> <MovieImage url={`https://image.tmdb.org/t/p/original/${movie.poster_path}`} name={movie.title} /> <MovieTextContainer title={movie.original_title} idGenre={movie.genre_ids} /> </div> )) )} </div> </div> ); }; export default TrendContainer;
3. 方案二:抽离自定义Hook(适合多组件复用请求逻辑)
如果多个组件都要用到电影相关的接口数据,可以把请求逻辑抽成自定义Hook复用:
// 新建hooks/useMovie.js文件 import {useState, useEffect} from 'react'; import Movie from '../models/Movie.js'; // 自定义Hook必须以use开头 export const useMovieTrends = () => { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { setLoading(true); try { const res = await Movie.GetMovieTrends(); setData(res.results || []); setError(null); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchData(); }, []); return {data, loading, error}; }; export const useMovieGenres = () => { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { setLoading(true); try { const res = await Movie.GetMovieGenres(); setData(res || []); setError(null); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchData(); }, []); return {data, loading, error}; };
在组件内直接调用自定义Hook即可:
// TrendContainer.jsx中使用 import { useMovieTrends } from '../hooks/useMovie.js'; const TrendContainer = () => { const {data: movieTrends, loading} = useMovieTrends(); // 剩下的渲染逻辑和方案一一致 }
内容的提问来源于stack exchange,提问作者Kkzio
相关产品推荐
相关产品推荐

