#javascript #reactjs #react-component #react-functional-component #react-state
Вопрос:
Я создаю приложение для фильмов в React и использую для этого api TMDB. Я совершенно новичок в функциональных компонентах, и у меня есть несколько запросов. Сначала я извлекаю все жанры с помощью api, а затем повторяю жанры один за другим и вызываю другой API для извлечения 20 фильмов в этом жанре.
Окончательные данные должны выглядеть следующим образом:
[{
genre: 'action',
movies: [],
},
{
genre: 'adventure',
movies: [],
}]
Так что я могу повторить приведенный выше массив объектов и отобразить жанр и соответствующие ему фильмы.
Прямо сейчас я застрял с приведенным ниже кодом.
const [movieData, setMovieData] = useState([]);
const fetchGenres = async () => {
const url = `https://api.themoviedb.org/3/genre/movie/list?api_key=xxxamp;language=en-US`;
try {
const res = await fetch(url);
const data = await res.json();
setMovieData(data.genres);
} catch (err) {
console.error(err);
}
}
Теперь перейдем к вопросам,
- Прямо сейчас movieData представляет собой массив и состоит только из жанров. Как добавить фильмы и изменить их на массив объектов ?
- Как перебирать жанры и устанавливать соответствующие фильмы в movieData?
- Любой другой простой/эффективный способ сделать это?
Решения очень ценятся!
Комментарии:
1. Было бы полезно, если бы вы могли добавить образец json вашего api.
2. Ваш окончательный вывод: почему это должен быть массив объектов? Один объект имел бы больше смысла.
{ action: [], adventure: [] }
.3. Ссылка @HassanImam Codesandbox, содержащая оба api : codesandbox.io/s/infallible-greider-vf18t?file=/src/App.js
4. @Энди, не могли бы вы, пожалуйста, объяснить, как это сделать? Может быть, краткое решение?
Ответ №1:
На мой взгляд, лучше хранить ваши данные отдельно. Я имею в виду множество жанров и множество фильмов. Так что у вас будет:
const [genres, setGenres] = useState([]);
const [movies, setMovies] = useState([]);
затем вы выполняете асинхронный вызов для извлечения жанров и устанавливаете его в состояние:
const fetchGenres = async () => {
const url = `https://baseurl/3/genre/movie/list?api_key=tokenamp;language=en-US`;
try {
const res = await fetch(url);
const data = await res.json();
setGenres(data.genres);
} catch (err) {
console.error(err);
}
};
React.useEffect(()=>{
fetchGenres();
},[]);
чтобы узнать, были ли заданы жанры, вы можете создать эффект, который будет обновляться каждый раз при изменении жанров:
async function fetchMovies() {
let result = [];
for (const genre of genres) {
const url = `https://baseurl/3/discover/movie?api_key=tokenamp;with_genres=${genre.id}`;
const res = await fetch(url);
const { results } = await res.json();
result = [...result, ...results];
}
setMovies(result);
}
React.useEffect(() => {
fetchMovies();
}, [genres]);
и, наконец, мы можем использовать React.useMemo для вычисления массива результатов, который нам нужен в соответствии с жанрами и фильмами, он будет пересчитываться каждый раз, когда меняется один из внутренних параметров:
const resultArray = React.useMemo(() => {
return genres.map(genre => ({
genre,
movies: movies.filter((movie) => movie.genre_ids.includes(genre.id))
}));
}, [genres, movies])
Я верю, что вы получите массив, подобный этому
[{
genre: 'action',
movies: [],
},
{
genre: 'adventure',
movies: [],
}]
Комментарии:
1. Я выполнил те же действия, но получил синтаксическую ошибку в части resultArray. Ссылка на Codesandbox для вашей справки : [ссылка] codesandbox.io/s/infallible-greider-vf18t?file=/src/App.js
2. Конечно, извините, что я исправил эту часть кода, я забыл скобки {}
3. Это ваш рабочий пример codesandbox.io/s/sleepy-perlman-it6c3
Ответ №2:
Как вы уже сказали, сначала вам нужно получить массив жанров. Затем вы должны выполнить итерацию по этому массиву и создать список обещаний для получения соответствующих фильмов. Как только эти обещания будут решены (с помощью Promise.all
функции), вы можете обновить переменную состояния movieData
.
const GENRE_URL = `https://.../genre/movie/list?api_key=xxxamp;language=en-US`;
const MOVIE_URL = `https://.../movie/list?api_key=xxxamp;language=en-USamp;genre=`;
async function fetchGenres() {
try {
const genres = await fetch(GENRE_URL).then(res => res.json());
const moviePromises = genres.map(genre => {
return fetch(MOVIE_URL genre)
.then(res => res.json())
// build an object containing 2 keys `genre` and `movies`
.then(movies => ({genre, movies}));
});
return await Promise.all(moviePromises);
} catch (err) {
console.error(err);
throw err;
}
}
вы должны адаптировать MOVIE_URL
его к своим потребностям, я действительно не знаю, каков реальный URL-адрес, я полагаю, вам нужно передать значение жанра в качестве параметра пути или параметра запроса.
теперь вы можете вызвать эту функцию в useEffect
крючке, чтобы загрузить фильмы при отображении компонента.
function App() {
const [movieData, setMovieData] = useState([]);
useEffect(() => {
fetchMoviesGroupByGenre()
.then(data => setMovieData(data))
.catch(...);
}, []);
return ...
}
С точки зрения производительности вы ничего не будете показывать до того, как все фильмы будут извлечены.
Если это проблема для вас, вы можете обновить переменную состояния, как только api даст вам ответ
async function fetchGenres() {
try {
const genres = await fetch(GENRE_URL).then(res => res.json());
const moviePromises = genres.map(genre => {
return fetch(MOVIE_URL genre)
.then(res => res.json())
.then(movies => {
const item = {genre, movies};
setMovieData(prevMovieData => [...prevMovieData, item]);
return item;
]);
});
return await Promise.all(moviePromises);
} catch (err) {
console.error(err);
throw err;
}
}
Ответ №3:
Из того, что я понял, что вы хотите сделать, это
setMovieData(prevMovieData => ([
...prevMovieData,
{ genre: genreFromApiCall, movies: moviesFromApiCall }
])
)
Комментарии:
1. Пожалуйста, добавьте дополнительные сведения, чтобы расширить свой ответ, например, ссылки на рабочий код или документацию.