Tab

95 阅读1分钟

吃完饭不好意思马上回家,写一种基于组合模式的Tab实现,有机会放到业务代码里:

import React, {
  Children,
  isValidElement,
  cloneElement,
  useState,
  useRef,
} from "react";

import "./styles.css";

interface TabItemProps {
  title: React.ReactNode;
  id: string | number;
  content: React.ReactNode;
}

interface TabContainerProps {
  onChange?(id: string | number): void;
  defaultRenderId?: string | number;
  currentId?: string | number;
}

// @ts-ignore
const TabContainer: React.FC<React.PropsWithChildren<TabContainerProps>> = (
  props
) => {
  const { children, onChange, defaultRenderId, currentId: outerId } = props;
  const [currentRenderId, updateCurId] = useState<string | number>(
    defaultRenderId || ""
  );
  const contentRef = useRef<Map<number | string, React.ReactNode>>(new Map());

  if (outerId && outerId !== currentRenderId) {
    updateCurId(outerId);
  }

  return (
    <>
      <div>------------ 这里是Tab --------------</div>
      <div className="tab-container">
        {Children.map(children, (child) => {
          if (isValidElement(child)) {
            const childProps = child.props;
            const id: string | number = Reflect.get(childProps, "id");
            const content: React.ReactNode = Reflect.get(childProps, "content");

            if (id && content) {
              contentRef.current.set(id, content);

              return cloneElement<any>(child, {
                onChange: () => {
                  updateCurId(id);
                  onChange?.(currentRenderId);
                },
              });
            } else {
              return null;
            }
          }

          return null;
        })}
      </div>
      ------------ 下面是内容区域 --------------
      <div>{contentRef.current.get(currentRenderId)}</div>
    </>
  );
};

const TabItem: React.FC<TabItemProps> = (props) => {
  const { title } = props;
  const onChange = Reflect.get(props, "onChange") as () => void;

  return (
    <div onClick={() => onChange?.()} className="tab-item">
      {title}
    </div>
  );
};

export const Tab: React.FC = () => {
  const [curId, updateCurId] = useState<string | number>("");
  const tempId = useRef<string | number>("");

  return (
    <>
      <input
        placeholder="current id"
        onChange={(e) => {
          tempId.current = e.target.value;
        }}
      />
      <button
        children="格老子锁死"
        onClick={() => {
          updateCurId(tempId.current);
        }}
      />
      <TabContainer
        onChange={(id) => {
          console.log("current tab id", id);
        }}
        defaultRenderId={"solid"}
        currentId={curId}
      >
        <TabItem
          title="react"
          id="react"
          content={<div style={{ backgroundColor: "red" }}>learn react</div>}
        />
        <TabItem
          title="vue"
          id="vue"
          content={<div style={{ backgroundColor: "yellow" }}>learn vue</div>}
        />
        <TabItem
          title="solid"
          id="solid"
          content={<div style={{ backgroundColor: "blue" }}>learn solid</div>}
        />
        asdasd asfasdf
        <>123</>
      </TabContainer>
    </>
  );
};

效果:

感觉应该升序生成id,下次再改