动手打造antd组件之Carousel

744 阅读4分钟

本文正在参加「金石计划」

一:引言

本篇文章仿写antd组件库的Carousel组件,也就是我们常用的轮播图(swiper)。文章重点是模拟Carousel组件的内部功能实现。

二:组件分析

观察下图,我们设计一个可配置的轮播图组件,其配置项包含如下

image.png

  • className 类名
  • autoplay 是否自定轮播
  • duration 轮播间隔时间
  • onChange 轮播图切换触发回调函数

三:问题拆解

在设计组件前,我们可以先考子问题,最后组装起来就实现了目标组件。

问题1:如何控制轮播子元素的展示隐藏?

  • 为了简单,我们通过css的display:block和none来控制当前元素的显示与隐藏。
  • 初始时设置index为0标记第一个元素展示,当点击轮播控制或自动轮播时更新index

问题2:如何用定时器控制轮播?

这个比较容易实现,根据用户是否传递autoplay属性控制自动轮播,如果autoplay是true,我们可以在useEffect的第一次加载阶段开启定时器控制轮播图index的变化。

问题3:如何对组件内部的子元素添加index和样式控制?

为了对组件内部传递的子元素进行二次处理,我们需要借助React.Children.map方法对传递的子元素进行二次封装。通过React.cloneElement对每个传递的子元素添加动态index和class类名来控制不同展示。

四:React.Children.map介绍

为了后续工作的进展,我们必须学会React.Children.map这个api。

children属性介绍

每个组件都会在props上保存一个children属性,其保存自己包裹的组件,元素,文本等。

index.tsx

import Box from "./Box";
const Home = () => {
  return (
      <Box>
        <div>1</div>
        <p>2</p>
        3
      </Box>
  )
}
export default Home;

Box.tsx

 import React from "react";
const Box = (props:any)=>{
  const {children} = props;
  console.log(children)
  return (
    <div>box</div>
  )
}
export default Box;

image.png

通过输出观察到,box组件包裹的div,p,文本都被children属性保存起来了。

React.children.map

学习了children属性,我们可以介绍React.children.map这个api了。它接受两个参数,第一个参数是上面介绍的children对象,保存了当前组件内部包裹的所有元素。第二个参数是一个回调函数,其功能和Array.map一模一样,可以对其内部的children的每一个子元素进行二次处理。通常我们会借助React.cloneElement去给当前遍历的元素添加想要的属性。

React.children.map最后返回的是一个二次处理后的dom节点。

注意点:React.children.map对组件内部元素的封装大都需要结合React.cloneElement去完成

React.children.map(children,(item,index)=>{
    return ...
})

示例

index.tsx

import Box from "./Box";
const Home = () => {
  return (
      <Box>
        <div>1</div>
        <div>2</div>
        <div>3</div>
      </Box>
  )
}
export default Home;

Box.tsx

import React from "react";
const Box = (props:any)=>{
  const {children} = props;
  return (
    <div>
        {  React.Children.map(children,(item,index)=>{
             return React.cloneElement(item,{index})
           })
        }
    </div>
  )
}
export default Box;

效果展示

我们通过 React.children.map和React.cloneElement对box组件内部传递的元素进行了二次封装处理,给每个元素都添加了index属性

image.png

五:轮播图代码实现

import React,{useEffect,useState} from "react";
import classNames from "classnames";
interface SwiperProps {
  autoplay?:boolean,
  children?:React.ReactNode,
  className?:string,
  duration?:number,
  onChange?:(e:any)=>void
}
const Swiper = (props:SwiperProps) => {
  const {autoplay,className,children,duration,onChange} = props;
  const [activedIndex,setActivedIndex] = useState(0);
  const [count,setCount] = useState(0);
  
  //根据autoplay判断是否开启轮询定时器
  useEffect(()=>{
    let timer:any = null;
    if(autoplay) {
      timer = setInterval(()=>{
        let index = activedIndex+1;
        if(index===count) {
          index = 0;   
        }
        setActivedIndex(index);
        onChange && onChange(index)
      },duration);
    }
    return () => {
      timer && clearInterval(timer);
    }
  },[activedIndex])
   
  useEffect(()=>{
    if(!children) {
      console.error('至少传递一个子元素');
      return;
    }
    if(Array.isArray(children)) setCount(children.length);
    else{
      setCount(0);
    }
  },[])

  //接受用户传递的className
  const classes = classNames('swiper',className);
  
  //轮播图改变时触发回调
  const changeIndex = (index:number) => {
    setActivedIndex(index);
    onChange && onChange(index);
  }
  
  //React.Children.map对组件传递的子元素进行处理,添加index标记和class控制
  const swiperChilds = (
    <>
    {
      React.Children.map(children,(item:any,index:number)=>{
        return (
          <div 
            key={index} 
            className={index===activedIndex?'swiper-item swiper-item-actived':'swiper-item'}
          >
            { React.cloneElement(item,{
                index:index
              })
            }
          </div>
        )
      })
    }
    </>
  ) 
  return (
    <div className={classes}>
      {swiperChilds}
      <ul className="swiper-control">
        {Array.from(new Array(count)).map((item,index)=>(
          <li 
            onClick={()=>changeIndex(index)} 
            key={index}
            className={index===activedIndex?"swiper-control-item swiper-control-item-actived":"swiper-control-item"}
          >
          </li>
        ))}
      </ul>
    </div>
  )
}
Swiper.defaultProps = {
  duration:1500,
  autoplay:false
}
export default Swiper;

六:功能演示

演示1:默认状态

  <Swiper>
      <div>1</div>
      <div>2</div>
      <div>3</div>
 </Swiper>
 
 

1.gif

演示2:添加自动轮播

duration延迟时间属性默认为1500,可以不传递。

      <Swiper autoplay duration={2000}>
          <div>1</div>
          <div>2</div>
          <div>3</div>
     </Swiper>
     
   

2.gif

演示3:添加轮播图切换回调函数

  //e传递当前展示元素的index下标。
  const change = (e:any) => {
    console.log(e)
  }
  return (
    <div>
     <Swiper autoplay duration={2000} onChange={change}>
      <div>1</div>
      <div>2</div>
      <div>3</div>
     </Swiper>
    </div>
  )

3.gif

总结

今天的轮播图组件到此结束,希望大家多多支持,我们下一个组件见。