React18项目 折扣区域tabs的封装和切换 动态添加class 安装classnames useState初始化值(只渲染一次)的问题(有值才渲染)

156 阅读2分钟

折扣区域tabs的封装和切换

动态添加class 安装classnames

  • npm i classnames

子组件:

import PropTypes from "prop-types";
import React, { memo, useState } from "react";
import { TabsWrapper } from "./style";
import classNames from "classnames";

const SectionTabs = memo((props) => {
  const { tabNames = [], tabClick } = props;
  const [currentIndex, setCurrentIndex] = useState(0);

  function itemClickHandle(index, name) {
    setCurrentIndex(index);
    // 子传父
    tabClick(index, name);
  }

  return (
    <TabsWrapper>
      {tabNames.map((item, index) => {
        return (
          <div
            onClick={(e) => itemClickHandle(index, item)}
            key={index}
            className={classNames("item", { active: index === currentIndex })}
          >
            {item}
          </div>
        );
      })}
    </TabsWrapper>
  );
});

SectionTabs.propTypes = {
  tabNames: PropTypes.array,
};
export default SectionTabs;
import styled from "styled-components";

export const TabsWrapper = styled.div`
  display: flex;

  .item {
    box-sizing: border-box;
    flex-basis: 120px;
    flex-shrink: 0;
    padding: 14px 16px;
    margin-right: 16px;
    border-radius: 3px;
    font-size: 17px;
    text-align: center;
    border: 0.5px solid #d8d8d8;
    white-space: nowrap;
    cursor: pointer;
    ${(props) => props.theme.mixin.boxShadow};

    &:last-child {
      margin-right: 0;
    }

    &.active {
      color: #fff;
      background-color: ${(props) => props.theme.color.second};
    }
  }
`;

父组件:

  // 从redux获取数据
  const { goodPriceInfo, highScoreInfo, discountInfo } = useSelector(
    (state) => ({
      goodPriceInfo: state.home.goodPriceInfo,
      highScoreInfo: state.home.highScoreInfo,
      discountInfo: state.home.discountInfo,
    }),
    shallowEqual
  );

  // 数据的转换
  const tabNames = discountInfo.dest_address?.map((item) => item.name);
  const [name, setName] = useState("佛山");
  // 传递给子组件的函数
  const tabClickHandle = useCallback(function (index, item) {
    setName(item);
  }, []);
            {/* 折扣数据 */}
          <div className="discount">
            <SectionHeader title={discountInfo.title} subtitle={discountInfo.subtitle} />
            <SectionTabs tabNames={tabNames} tabClick={tabClickHandle} />
            <SectionRooms roomList={discountInfo.dest_list?.[name]} itemWidth="33.333%" />
          </div>

useState初始化值(只渲染一次)的问题(有值才渲染)

const initName = Object.keys(infoData.dest_list)[0];
const [name, setName] = useState(initName);
export function isEmptyO(obj) {
  return !!Object.keys(obj).length;
}

组件仅渲染一次,第一次为空,第二次网络请求的数据,使用&&则第一次为空不渲染。

    {isEmptyO(discountInfo) && <HomeSectionV2 infoData={discountInfo} />}
    {isEmptyO(goodPriceInfo) && <HomeSectionV1 infoData={goodPriceInfo} />}
    {isEmptyO(highScoreInfo) && <HomeSectionV1 infoData={highScoreInfo} />}

继续封装折扣区域组件: 子组件HomeSectionV2接收父组件Home传递过来的数据

import PropTypes from "prop-types";
import React, { memo, useCallback, useState } from "react";
import { SectionV2Wrapper } from "./style";
import SectionHeader from "@/components/section-header";
import SectionRooms from "@/components/section-rooms";
import SectionTabs from "@/components/section-tabs";
const HomeSectionV2 = memo((props) => {
  // 从props获取数据
  const { infoData } = props;

  const initName = Object.keys(infoData.dest_list)[0];
  const [name, setName] = useState(initName);

  // 数据的转换
  const tabNames = infoData.dest_address?.map((item) => item.name);
  // 传递给子组件的函数
  const tabClickHandle = useCallback(function (index, item) {
    setName(item);
  }, []);

  return (
    <SectionV2Wrapper>
      <SectionHeader title={infoData.title} subtitle={infoData.subtitle} />
      <SectionTabs tabNames={tabNames} tabClick={tabClickHandle} />
      <SectionRooms roomList={infoData.dest_list?.[name]} itemWidth="33.333%" />
    </SectionV2Wrapper>
  );
});

HomeSectionV2.propTypes = {
  infoData: PropTypes.object,
};
export default HomeSectionV2;
import styled from "styled-components";
export const SectionV2Wrapper = styled.div`
  margin-top: 30px;
`;

父组件使用HomeSectionV2

import React, { memo, useEffect } from "react";
import { shallowEqual, useDispatch, useSelector } from "react-redux";

import { HomeWrapper } from "./style";
import HomeBanner from "./c-cpns/home-banner";
import { fetchHomeDataAction } from "@/store/modules/home";
import HomeSectionV1 from "./c-cpns/home-section-v1";

import HomeSectionV2 from "./c-cpns/home-section-v2";
import { isEmptyO } from "@/utils";

const Home = memo(() => {
  // 从redux获取数据
  const { goodPriceInfo, highScoreInfo, discountInfo } = useSelector(
    (state) => ({
      goodPriceInfo: state.home.goodPriceInfo,
      highScoreInfo: state.home.highScoreInfo,
      discountInfo: state.home.discountInfo,
    }),
    shallowEqual
  );

  // 派发异步的事件:发送网络请求
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(fetchHomeDataAction("传参数data"));
  }, [dispatch]);

  return (
    <HomeWrapper>
      <HomeBanner />
      <div className="content">
        <div className="good-price">
          {isEmptyO(discountInfo) && <HomeSectionV2 infoData={discountInfo} />}
          {isEmptyO(goodPriceInfo) && <HomeSectionV1 infoData={goodPriceInfo} />}
          {isEmptyO(highScoreInfo) && <HomeSectionV1 infoData={highScoreInfo} />}
        </div>
      </div>
    </HomeWrapper>
  );
});
export default Home;

效果:

image.png

section-footer封装

icon:

import React, { memo } from "react";
import styleStrToObject from "./utils";
const IconArrow = memo(() => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 32 32"
      aria-hidden="true"
      role="presentation"
      focusable="false"
      style={styleStrToObject(
        "display: block; fill: none; height: 12px; width: 12px; stroke: currentcolor; stroke-width: 5.33333; overflow: visible;"
      )}
      cursorshover="true"
    >
      <path fill="none" d="m12 4 11.3 11.3a1 1 0 0 1 0 1.4L12 28"></path>
    </svg>
  );
});
export default IconArrow;

SectionFooter:

import PropTypes from "prop-types";
import React, { memo } from "react";
import { FooterWrapper } from "./style";
import IconArrow from "@/assets/svg/icon_arrow";
const SectionFooter = memo((props) => {
  const { name } = props;
  let showMsg = "显示全部";
  if (name) {
    showMsg = `显示更多${name}房源`;
  }

  return (
    <FooterWrapper color={name ? "#00848A" : "#000"}>
      <div className="info">
        <span className="text">{showMsg}</span>
        <IconArrow />
      </div>
    </FooterWrapper>
  );
});

SectionFooter.propTypes = {
  name: PropTypes.string,
};
export default SectionFooter;

样式:

import styled from "styled-components";
export const FooterWrapper = styled.div`
  display: flex;
  margin-top: 10px;

  .info {
    display: flex;
    align-items: center;
    cursor: pointer;
    font-size: 17px;
    font-weight: 700;
    color: ${(props) => props.color};

    &:hover {
      text-decoration: underline;
    }
    .text {
      margin-right: 6px;
    }
  }
`;

V1,V2中使用:

image.png image.png 效果:

image.png