You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写兼具复用性与可读性的多类型Slider组件?

问题

需要实现一个可渲染4种类型(banners、links、main、items)的Slider组件,同时兼顾复用性与代码可读性。当前基于type分支判断的实现存在大量重复代码,尝试用三元表达式优化后可读性大幅下降,不知道如何平衡二者,寻求技术方案。

现有实现代码

function Slider({
  title,
  titleStyle,
  children,
  type,
  paginationClass,
  isGrid,
  className,
}) {
  const isMobile = useMediaQuery({ maxWidth: 991 });

  const linksModules = isGrid ? [Grid, FreeMode] : [FreeMode];

  // banners
  if (type === "banners") {
    return (
      <div className="sliderBanners">
        {title && <h6 className={`title ${titleStyle}`}>{title}</h6>}
        {!isMobile && (
          <div className="carousel-recommendation-buttons">
            <button type="button" className="carousel-recommendation-prev">
              <PrevCarouselRec />
            </button>
            <button type="button" className="carousel-recommendation-next">
              <NextCarouselRec />
            </button>
          </div>
        )}
        <Swiper
          slidesPerView={isMobile ? "auto" : 4}
          freeMode
          preventClicks={false}
          preventClicksPropagation={false}
          pagination={{
            el: `.${paginationClass}`,
          }}
          modules={[Navigation, Pagination, FreeMode]}
          className="carouselItemsSlider"
          navigation={
            !isMobile && {
              nextEl: `.carousel-recommendation-next`,
              prevEl: `.carousel-recommendation-prev`,
            }
          }
          breakpoints={{
            320: {
              spaceBetween: 11,
            },
            480: {
              spaceBetween: 13,
            },
            991: {
              slidesPerView: 4,
              spaceBetween: 16,
            },
          }}
        >
          {children}
        </Swiper>
        <div
          className={`carousel-pagination itemsPagination ${paginationClass}`}
        />
      </div>
    );
  }

  // links
  if (type === "links") {
    return (
      <>
        {title && <h4 className={titleStyle}>{title}</h4>}
        <Swiper
          slidesPerView="auto"
          modules={linksModules}
          className={className}
          freeMode
          grid={
            isGrid && {
              rows: 2,
              fill: "row",
            }
          }
          breakpoints={{
            320: {
              spaceBetween: 7,
            },
            480: {
              spaceBetween: 14,
            },
            768: {
              spaceBetween: 20,
            },
          }}
        >
          {children}
        </Swiper>
      </>
    );
  }

  // main
  if (type === "main") {
    return (
      <div className="sliderMain">
        {title && <h6 className={`title ${titleStyle}`}>{title}</h6>}
        <button className="carousel-prev" type="button">
          <PrevCarousel />
        </button>
        <button className="carousel-next" type="button">
          <NextCarousel />
        </button>
        <Swiper
          slidesPerView={1}
          autoplay={{
            delay: 400000,
          }}
          pagination={{
            el: `.mainPagination`,
          }}
          observer
          observeParents
          modules={[Autoplay, Navigation, Pagination]}
          className="mainSlider"
          spaceBetween={27}
          loop
          navigation={{
            nextEl: `.carousel-next`,
            prevEl: `.carousel-prev`,
          }}
        >
          {children}
        </Swiper>
        <div className="carousel-pagination mainPagination" />
      </div>
    );
  }

  // items
  return (
    <div className="sliderContainer">
      {title && <h6 className={`title ${titleStyle}`}>{title}</h6>}
      {!isMobile && (
        <div className="carousel-recommendation-buttons">
          <button type="button" className="carousel-recommendation-prev">
            <PrevCarouselRec />
          </button>
          <button type="button" className="carousel-recommendation-next">
            <NextCarouselRec />
          </button>
        </div>
      )}
      <Swiper
        slidesPerView={4}
        autoplay={{
          delay: 400000,
          disableOnInteraction: true,
        }}
        pagination={{
          el: `.${paginationClass}`,
        }}
        observer
        observeParents
        modules={[Autoplay, Navigation, Pagination]}
        className="carouselItemsSlider"
        spaceBetween={27}
        loop
        navigation={{
          nextEl: `.carousel-recommendation-next`,
          prevEl: `.carousel-recommendation-prev`,
        }}
        breakpoints={{
          320: {
            slidesPerGroup: 2,
            slidesPerView: 2,
            spaceBetween: 12,
          },
          600: {
            slidesPerGroup: 3,
            slidesPerView: 3,
          },
          1040: {
            slidesPerGroup: 4,
            slidesPerView: 4,
            spaceBetween: 25,
          },
        }}
      >
        {children}
      </Swiper>
      <div
        className={`carousel-pagination itemsPagination ${paginationClass}`}
      />
    </div>
  );
}
优化方案

核心思路是提取配置+拆分通用子组件,把不同类型的差异点抽成配置对象,通用逻辑封装成子组件,既减少重复代码,又保持可读性。

1. 提取类型配置对象

把每种Slider的差异化参数(容器类名、标题标签、Swiper配置、导航按钮、分页配置等)集中到一个配置对象里,便于维护和扩展:

// 定义各类型Slider的配置
const sliderConfigs = {
  banners: {
    containerClass: "sliderBanners",
    titleTag: "h6",
    titleClass: (titleStyle) => `title ${titleStyle}`,
    showNavButtons: (isMobile) => !isMobile,
    navButtons: (
      <div className="carousel-recommendation-buttons">
        <button type="button" className="carousel-recommendation-prev">
          <PrevCarouselRec />
        </button>
        <button type="button" className="carousel-recommendation-next">
          <NextCarouselRec />
        </button>
      </div>
    ),
    swiperProps: (isMobile, paginationClass) => ({
      slidesPerView: isMobile ? "auto" : 4,
      freeMode: true,
      preventClicks: false,
      preventClicksPropagation: false,
      pagination: { el: `.${paginationClass}` },
      modules: [Navigation, Pagination, FreeMode],
      className: "carouselItemsSlider",
      navigation: !isMobile ? {
        nextEl: `.carousel-recommendation-next`,
        prevEl: `.carousel-recommendation-prev`,
      } : undefined,
      breakpoints: {
        320: { spaceBetween: 11 },
        480: { spaceBetween: 13 },
        991: { slidesPerView: 4, spaceBetween: 16 },
      }
    }),
    pagination: (paginationClass) => (
      <div className={`carousel-pagination itemsPagination ${paginationClass}`} />
    )
  },
  links: {
    containerClass: null, // 不需要外层容器,用Fragment
    titleTag: "h4",
    titleClass: (titleStyle) => titleStyle,
    showNavButtons: () => false,
    swiperProps: (_, __, isGrid) => ({
      slidesPerView: "auto",
      modules: isGrid ? [Grid, FreeMode] : [FreeMode],
      className: className,
      freeMode: true,
      grid: isGrid ? { rows: 2, fill: "row" } : undefined,
      breakpoints: {
        320: { spaceBetween: 7 },
        480: { spaceBetween: 14 },
        768: { spaceBetween: 20 },
      }
    }),
  },
  main: {
    containerClass: "sliderMain",
    titleTag: "h6",
    titleClass: (titleStyle) => `title ${titleStyle}`,
    showNavButtons: () => true,
    navButtons: (
      <>
        <button className="carousel-prev" type="button">
          <PrevCarousel />
        </button>
        <button className="carousel-next" type="button">
          <NextCarousel />
        </button>
      </>
    ),
    swiperProps: () => ({
      slidesPerView: 1,
      autoplay: { delay: 400000 },
      pagination: { el: `.mainPagination` },
      observer: true,
      observeParents: true,
      modules: [Autoplay, Navigation, Pagination],
      className: "mainSlider",
      spaceBetween: 27,
      loop: true,
      navigation: {
        nextEl: `.carousel-next`,
        prevEl: `.carousel-prev`,
      }
    }),
    pagination: () => <div className="carousel-pagination mainPagination" />
  },
  items: {
    containerClass: "sliderContainer",
    titleTag: "h6",
    titleClass: (titleStyle) => `title ${titleStyle}`,
    showNavButtons: (isMobile) => !isMobile,
    navButtons: (
      <div className="carousel-recommendation-buttons">
        <button type="button" className="carousel-recommendation-prev">
          <PrevCarouselRec />
        </button>
        <button type="button" className="carousel-recommendation-next">
          <NextCarouselRec />
        </button>
      </div>
    ),
    swiperProps: (_, paginationClass) => ({
      slidesPerView: 4,
      autoplay: { delay: 400000, disableOnInteraction: true },
      pagination: { el: `.${paginationClass}` },
      observer: true,
      observeParents: true,
      modules: [Autoplay, Navigation, Pagination],
      className: "carouselItemsSlider",
      spaceBetween: 27,
      loop: true,
      navigation: {
        nextEl: `.carousel-recommendation-next`,
        prevEl: `.carousel-recommendation-prev`,
      },
      breakpoints: {
        320: { slidesPerGroup: 2, slidesPerView: 2, spaceBetween: 12 },
        600: { slidesPerGroup: 3, slidesPerView: 3 },
        1040: { slidesPerGroup: 4, slidesPerView: 4, spaceBetween: 25 },
      }
    }),
    pagination: (paginationClass) => (
      <div className={`carousel-pagination itemsPagination ${paginationClass}`} />
    )
  }
};

2. 封装通用子组件

把重复出现的逻辑拆成通用子组件,减少重复代码:

通用标题组件

function SliderTitle({ tag: Tag, className, title }) {
  if (!title) return null;
  return <Tag className={className}>{title}</Tag>;
}

通用导航按钮组件

function SliderNavButtons({ show, children }) {
  if (!show) return null;
  return <>{children}</>;
}

3. 重构主Slider组件

基于配置对象和通用组件,重构主组件,逻辑清晰且复用性强:

function Slider({
  title,
  titleStyle,
  children,
  type,
  paginationClass,
  isGrid,
  className,
}) {
  const isMobile = useMediaQuery({ maxWidth: 991 });
  const config = sliderConfigs[type];
  if (!config) return null; // 处理未知类型

  const {
    containerClass,
    titleTag,
    titleClass,
    showNavButtons,
    navButtons,
    swiperProps,
    pagination
  } = config;

  // 生成各部分组件
  const titleComponent = <SliderTitle tag={titleTag} className={titleClass(titleStyle)} title={title} />;
  const navButtonsComponent = <SliderNavButtons show={showNavButtons(isMobile)}>{navButtons}</SliderNavButtons>;
  const swiperComponent = <Swiper {...swiperProps(isMobile, paginationClass, isGrid)}>{children}</Swiper>;
  const paginationComponent = pagination?.(paginationClass);

  // 渲染内容主体
  const content = (
    <>
      {titleComponent}
      {navButtonsComponent}
      {swiperComponent}
      {paginationComponent}
    </>
  );

  // 根据配置决定是否添加外层容器
  return containerClass ? <div className={containerClass}>{content}</div> : content;
}

优化优势

  • 复用性:所有通用逻辑集中处理,新增类型只需在sliderConfigs里添加配置,无需重复编写组件结构
  • 可读性:配置对象清晰展示每种类型的差异,主组件逻辑简洁,一目了然
  • 可维护性:修改某类型的参数(比如Swiper的breakpoints)只需修改对应配置项,无需在多个分支里查找修改

内容的提问来源于stack exchange,提问作者AiroLie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 12:25:22