0

我正在尝试使用 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>
     }
4

1 回答 1

1

问题解决了!我认为 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>
     }
于 2020-10-10T23:15:19.153 回答