我正在尝试使用 React 构建电影搜索网络应用程序。我查询 themoviedb.com 以获取电影信息并向用户显示海报/电影标题。
我正在使用 Swiperjs 向用户水平显示电影海报和标题。不幸的是,当我将电影信息 map() 到 SwiperSlide 组件时,滑块不会滑动。它似乎结结巴巴,拒绝从第一张电影海报上移开。任何人都可以向我发送正确的方向,为什么它不起作用?
奇怪的是,在随机刷新时,随机流派将完全正常工作,直到重新加载,但只有一个流派组件会这样做。
流派组件:
import React from "react";
import { Swiper, SwiperSlide } from 'swiper/react';
import MovieCard from '../components/movieCard';
class Genre extends React.Component {
constructor(props) {
super(props);
this.state = {
genre: this.props.genre,
genreName: "",
movies: []
};
}
componentDidMount() {
const genreNum = this.state.genre;
const url = `apiURlRequestHere`;
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ movies: data.results }));
} catch (err) {
console.error(err);
}
this.getGenreName(genreNum);
}
getGenreName(genreNum) {
let title = "";
switch (genreNum) {
case "28":
title = "Action";
break;
case "12":
title = "Adventure";
break;
case "16":
title = "Animation";
break;
case "35":
title = "Comedy";
break;
case "80":
title = "Crime";
break;
case "99":
title = "Documentary";
break;
case "18":
title = "Drama";
break;
case "14":
title = "Fantasy";
break;
case "27":
title = "Horror";
break;
case "9648":
title = "Mystery";
break;
case "10749":
title = "Romance";
break;
case "878":
title = "Science Fiction";
break;
case "53":
title = "Thriller";
break;
default:
title = "";
break;
}
this.setState({ genreName: title });
}
render() {
const movies = this.state.movies;
let genreCat = this.state.genreName;
return (
<>
<h2 className="category-title">{genreCat}</h2>
<Swiper spaceBetween={0} slidesPerView={1}>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie} />
</SwiperSlide>
))}
</Swiper>
</>
);
}
}
export default Genre;
电影卡组件:
import React from "react";
export default function MovieCard({movie}) {
return (
<div className="card">
<img
className="card--image"
src={`https://image.tmdb.org/t/p/w185_and_h278_bestv2/${movie.poster_path}`}
alt={movie.title + ' poster'}
/>
<div className="card--content">
<h3 className="card--title">{movie.title}</h3>
<p className="card--rating">{movie.vote_average * 10}%</p>
</div>
</div>
)
}
- - 更新 - -
这是我从 SwiperJs 使用的Swiper组件
import React, { useRef, useState, useEffect, forwardRef } from 'react';
import { getParams } from './get-params';
import { initSwiper } from './init-swiper';
import { needsScrollbar, needsNavigation, needsPagination, uniqueClasses } from './utils';
import { renderLoop, calcLoopedSlides } from './loop';
import { getChangedParams } from './get-changed-params';
import { getChildren } from './get-children';
import { updateSwiper } from './update-swiper';
import { renderVirtual, updateOnVirtualData } from './virtual';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
const Swiper = forwardRef(
(
{
className,
tag: Tag = 'div',
wrapperTag: WrapperTag = 'div',
children,
onSwiper,
...rest
} = {},
externalElRef,
) => {
const [containerClasses, setContainerClasses] = useState('swiper-container');
const [virtualData, setVirtualData] = useState(null);
const [breakpointChanged, setBreakpointChanged] = useState(false);
const initializedRef = useRef(false);
const swiperElRef = useRef(null);
const swiperRef = useRef(null);
const oldPassedParamsRef = useRef(null);
const oldSlides = useRef(null);
const nextElRef = useRef(null);
const prevElRef = useRef(null);
const paginationElRef = useRef(null);
const scrollbarElRef = useRef(null);
const { params: swiperParams, passedParams, rest: restProps } = getParams(rest);
const { slides, slots } = getChildren(children);
const changedParams = getChangedParams(
passedParams,
oldPassedParamsRef.current,
slides,
oldSlides.current,
);
oldPassedParamsRef.current = passedParams;
oldSlides.current = slides;
const onBeforeBreakpoint = () => {
setBreakpointChanged(!breakpointChanged);
};
Object.assign(swiperParams.on, {
_containerClasses(swiper, classes) {
setContainerClasses(classes);
},
_swiper(swiper) {
swiper.loopCreate = () => {};
swiper.loopDestroy = () => {};
if (swiperParams.loop) {
swiper.loopedSlides = calcLoopedSlides(slides, swiperParams);
}
swiperRef.current = swiper;
if (swiper.virtual && swiper.params.virtual.enabled) {
swiper.virtual.slides = slides;
swiper.params.virtual.cache = false;
swiper.params.virtual.renderExternal = setVirtualData;
swiper.params.virtual.renderExternalUpdate = false;
}
},
});
if (swiperRef.current) {
swiperRef.current.on('_beforeBreakpoint', onBeforeBreakpoint);
}
useEffect(() => {
return () => {
if (swiperRef.current) swiperRef.current.off('_beforeBreakpoint', onBeforeBreakpoint);
};
});
// set initialized flag
useEffect(() => {
if (!initializedRef.current && swiperRef.current) {
swiperRef.current.emitSlidesClasses();
initializedRef.current = true;
}
});
// watch for params change
useIsomorphicLayoutEffect(() => {
if (changedParams.length && swiperRef.current && !swiperRef.current.destroyed) {
updateSwiper(swiperRef.current, slides, passedParams, changedParams);
}
});
// update on virtual update
useIsomorphicLayoutEffect(() => {
updateOnVirtualData(swiperRef.current);
}, [virtualData]);
// init swiper
useIsomorphicLayoutEffect(() => {
if (externalElRef) {
externalElRef.current = swiperElRef.current;
}
if (!swiperElRef.current) return;
initSwiper(
{
el: swiperElRef.current,
nextEl: nextElRef.current,
prevEl: prevElRef.current,
paginationEl: paginationElRef.current,
scrollbarEl: scrollbarElRef.current,
},
swiperParams,
);
if (onSwiper) onSwiper(swiperRef.current);
// eslint-disable-next-line
return () => {
if (swiperRef.current && !swiperRef.current.destroyed) {
swiperRef.current.destroy();
}
};
}, []);
// bypass swiper instance to slides
function renderSlides() {
if (swiperParams.virtual) {
return renderVirtual(swiperRef.current, slides, virtualData);
}
if (!swiperParams.loop || (swiperRef.current && swiperRef.current.destroyed)) {
return slides.map((child) => {
return React.cloneElement(child, { swiper: swiperRef.current });
});
}
return renderLoop(swiperRef.current, slides, swiperParams);
}
return (
<Tag
ref={swiperElRef}
className={uniqueClasses(`${containerClasses}${className ? ` ${className}` : ''}`)}
{...restProps}
>
{slots['container-start']}
{needsNavigation(swiperParams) && (
<>
<div ref={prevElRef} className="swiper-button-prev" />
<div ref={nextElRef} className="swiper-button-next" />
</>
)}
{needsScrollbar(swiperParams) && <div ref={scrollbarElRef} className="swiper-scrollbar" />}
{needsPagination(swiperParams) && (
<div ref={paginationElRef} className="swiper-pagination" />
)}
<WrapperTag className="swiper-wrapper">
{slots['wrapper-start']}
{renderSlides()}
{slots['wrapper-end']}
</WrapperTag>
{slots['container-end']}
</Tag>
);
},
);
Swiper.displayName = 'Swiper';
export { Swiper };
这是 SwiperSlide 组件:
import React, { useRef, useState, forwardRef } from 'react';
import { uniqueClasses } from './utils';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
const SwiperSlide = forwardRef(
(
{ tag: Tag = 'div', children, className = '', swiper, zoom, virtualIndex, ...rest } = {},
externalRef,
) => {
const slideElRef = useRef(null);
const [slideClasses, setSlideClasses] = useState('swiper-slide');
function updateClasses(swiper, el, classNames) {
if (el === slideElRef.current) {
setSlideClasses(classNames);
}
}
useIsomorphicLayoutEffect(() => {
if (externalRef) {
externalRef.current = slideElRef.current;
}
if (!slideElRef.current || !swiper) return;
if (swiper.destroyed) {
if (slideClasses !== 'swiper-slide') {
setSlideClasses('swiper-slide');
}
return;
}
swiper.on('_slideClass', updateClasses);
// eslint-disable-next-line
return () => {
if (!swiper) return;
swiper.off('_slideClass', updateClasses);
};
});
let slideData;
if (typeof children === 'function') {
slideData = {
isActive:
slideClasses.indexOf('swiper-slide-active') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate-active') >= 0,
isVisible: slideClasses.indexOf('swiper-slide-visible') >= 0,
isDuplicate: slideClasses.indexOf('swiper-slide-duplicate') >= 0,
isPrev:
slideClasses.indexOf('swiper-slide-prev') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate-prev') >= 0,
isNext:
slideClasses.indexOf('swiper-slide-next') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate next') >= 0,
};
}
const renderChildren = () => {
return typeof children === 'function' ? children(slideData) : children;
};
return (
<Tag
ref={slideElRef}
className={uniqueClasses(`${slideClasses}${className ? ` ${className}` : ''}`)}
data-swiper-slide-index={virtualIndex}
{...rest}
>
{zoom ? (
<div
className="swiper-zoom-container"
data-swiper-zoom={typeof zoom === 'number' ? zoom : undefined}
>
{renderChildren()}
</div>
) : (
renderChildren()
)}
</Tag>
);
},
);
SwiperSlide.displayName = 'SwiperSlide';
export { SwiperSlide };
另外,我在这里有一个关于这个问题的工作示例...... https://codesandbox.io/s/blue-mountain-0fjyc?file=/src/App.js 在这个例子中,点击浏览器上的刷新,那就是我所看到的。
- - 更新 - -
仍在做这个项目,我创建了一个电影详细信息页面,显示带有 Swiper 的演员表(我知道这真是个傻瓜)。它工作得很好,但如果我用下面的 MovieDetail 组件中的工作代码替换 GenreComponent 代码中的代码,它仍然无法工作......
import React from "react";
import { Swiper, SwiperSlide } from 'swiper/react';
import MovieGenre from '../components/movieGenreBtn';
class MovieDetails extends React.Component {
constructor(props) {
super(props);
this.state = {
movie: [],
movieGenres: [],
credits: [],
director: [],
foundDirector: false
}
}
componentDidMount() {
const movieId = this.props.location.pathname.replace("/", "");
this.fetchMovie(movieId);
this.fetchCrew(movieId);
}
fetchMovie(movieId) {
const url = `https://api.themoviedb.org/3/movie/${movieId}?api_key=${process.env.REACT_APP_MOVIE_API_KEY}&language=en-US
`;
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ movie: data }));
} catch (err) {
console.error(err);
}
}
fetchCrew(movieId) {
const url = `https://api.themoviedb.org/3/movie/${movieId}/credits?api_key=${process.env.REACT_APP_MOVIE_API_KEY}`
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ credits: data }));
} catch (err) {
console.error(err);
}
}
getDirector() {
let director = [];
if (this.state.credits !== null){
const crew = this.state.credits.crew;
let i;
for(i=0; i < crew.length; i++) {
if (crew[i].job === 'Director') {
director = crew[i];
}
}
}
this.setState({ director: director, foundDirector: true });
}
getGenres() {
const movie = this.state.movie;
let genres = [];
let i;
for (i=0; i < movie.genres.length; i++) {
genres.push(movie.genres[i]);
}
this.setState({ movieGenres: genres });
}
render() {
const movie = this.state.movie;
const cast = this.state.credits.cast;
if (cast != null && !this.state.foundDirector) {
this.getDirector();
this.getGenres();
}
const currCast = this.state.credits.cast;
const movieGenres = this.state.movieGenres;
return (
<div className="movie-details-wrapper">
<div className="details-header">
<img
className="movie-backdrop"
src={`https://image.tmdb.org/t/p/w1000_and_h450_multi_faces/${movie.backdrop_path}`} />
<h3 className="details-title">
{movie.title}
</h3>
<h5 className="details-tagling">
{movie.tagline}
</h5>
</div>
<div className="details-content">
<div className="details-director-rating">
<p className="basic-details">
Director: {this.state.director.name}
</p>
<p className="details-rating">
{movie.vote_average * 10}%
</p>
</div>
<div className="details-genres">
{movieGenres.map(genre => (
<MovieGenre genre={genre.id} />
))}
</div>
<div className="details-cast-wrapper">
<h3>Cast</h3>
<div className="details-cast">
{currCast ?
<Swiper>
{currCast.map(person => (
<SwiperSlide>
<img className="cast-img"src={`https://image.tmdb.org/t/p/w220_and_h330_bestv2/${person.profile_path}`} />
// </SwiperSlide>
))}
</Swiper>
:
<h2>No cast</h2>
}
</div>
</div>
</div>
</div>
);
}
} export default MovieDetails
--- 最后更新 --- 问题已解决!我认为 Swiper 在任何东西进入之前就已初始化,因此 Swiper 认为它的幻灯片为零并且无法运行。我通过添加一些条件渲染来检查电影变量的长度来解决这个问题。最初,这不适用于条件渲染,因为我忘记添加长度检查。
前:
<h2 className="category-title">{genreCat}</h2>
{movies ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
后:
<h2 className="category-title">{genreCat}</h2>
{movies.length > 0 ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}