组件搭建1 Tabs

Tabs帮助我们在有限的页面上切换显示不同的内容面板,通过标签页的形式提升内容的组织性和用户体验。

在这里我们以Antd的Tabs组件为例,拆解Tabs组件包含哪些内容。

https://ant.design/components/tabs-cn#tabs-demo-custom-indicator

主要分为上中下三层

  • 第一层:标签
  • 第二层:指示条
  • 第三层:标签内容容器

通过点击标签,指示条和内容容器也会出现相应的变化。

首先我们的Tabs组件接收一个items数组作为传入参数渲染。

//key作为唯一标识符
//label是标签显示的文字内容
//children是内容容器的内容
const items = [
  {
    key:"1",
    label:"label1",
    children:"children1",
  },
	{
    key:"2",
    label:"label2",
    children:"children2",
  }
]

现在将它传入组件当中。

export default Tabs = ({items}) => {
  
}

接下来,我们要在这个组件中,实现Tabs的基础功能。

标签实现

标签需要根据items传入的对象个数来进行渲染。这里我们可以用map进行遍历。

items.map((item) => {
  return (
		<div
				key = {item.key} //唯一值
        onClick = {}
		>
          {item.label} //标签内容
   </div>
)
})

现在实现了多个标签的显示,但我们还需要实现它的点击效果。
当我们点击其中一个标签的时候,显示这个标签的内容。

//首先我们需要一个参数来记录当前的标签值,并赋予默认值。
const [activeIndex,setActiveIndex] = useState(0);

//现在实现onClick方法
onClick = {() => setActiveIndex(item.key-1)}
//因为在map中的下标从0开始,但是我们一般定义的key值是从1开始,所以这里要-1才能正确渲染对于的内容。
//也可以让items中的key值从0开始

那么现在整合上面的内容,我们就实现了一个完整的多标签显示。

export default Tabs = ({items}) => {
  const [activeIndex,setActiveIndex] = useState(0);
  return 
		items.map((item) => {
  		return (
				<div
						key = {item.key} //唯一值
        		onClick = {() => setActiveIndex(item.key-1)}
						>
         		 {item.label} //标签内容
  		 </div>
		 );
		 })

};

指示条实现

指示条我们可以看作是一个高度极小,但保持一定宽度的div通过平滑的CSS过渡效果实现的。

我们需要计算标签div的位置,才能在它的下方定位指示条的位置。可以用Ref来绑定标签div。

再用它来访问 childrenoffsetWidthoffsetLeft 等原生 DOM 属性来定位指示条位置。

const tabsRef = useRef(null)
//定义指示条样式
const [indicatorStyle,setIndicatorStyle] = useState({})

useEffect = (() =>{
  //如果tabs的标签div不存在则直接返回
	if(!tabsRef.current){
    return;
  }
  //获取当前激活的Tabs位置key
  const currentTab = tabsRef.current.children[activeIndex];
  if (currentTab) {
    				//设置指示条的宽度与开始位置
            setIndicatorStyle({
                width: currentTab.offsetWidth - 20,
                left: currentTab.offsetLeft + 10,
            });
        }
},[activeIndex]);//根据activeIndex变化触发Effect


return 
//设置绝对定位,标签div设置为相对定位
	<div
  	style={
      absolute,
      ...indicatorStyle
    }
  />

标签和指示条都已经实现了。还剩下Childrend的内容显示。

标签内容容器实现

<div className="p-4">{items[activeIndex].children}</div>

加上样式后的完整代码

import React, { useState, useRef, useEffect } from "react";

/**
 * Tabs组件
 * @param {any} {items}
 * @returns {any}
 */
const Tabs = ({ items }) => {
    const [activeIndex, setActiveIndex] = useState(0);
    const [indicatorStyle, setIndicatorStyle] = useState({});
    const tabsRef = useRef(null);

    useEffect(() => {
        if (!tabsRef.current) {
            return;
        }
        const currentTab = tabsRef.current.children[activeIndex];
        if (currentTab) {
            setIndicatorStyle({
                width: currentTab.offsetWidth - 20,
                left: currentTab.offsetLeft + 10,
            });
        }
    }, [activeIndex]);

    return (
        <div className="p-4">
            <div className="flex h-10 w-full relative" ref={tabsRef}>
                {items.map((item) => {
                    return (
                        <div
                            key={item.key}
                            onClick={() => setActiveIndex(item.key - 1)}
                            style={{
                                height: "40px",
                                width: "80px",
                                display: "flex",
                                justifyContent: "center",
                                alignItems: "center",
                                margin: "0 10px",
                                cursor: "pointer",
                                color: activeIndex === item.key - 1 ? "#3875f6" : "black",
                            }}
                        >
                            {item.label}
                        </div>
                    );
                })}
                <div
                    className="absolute bottom-0 left-0 h-1 bg-blue-500 transition-all duration-300"
                    style={indicatorStyle}
                />
            </div>

            <div className="p-4">{items[activeIndex].children}</div>
        </div>
    );
};

export default Tabs;