如何编写兼具复用性与可读性的多类型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
相关产品推荐
相关产品推荐

