React18 项目 水平滚动组件封装 首页-向往数据的请求和滚动展示 首页-plus数据的请求和展示过程

140 阅读1分钟

水平滚动组件封装

import React, { memo, useEffect, useRef, useState } from "react";
import { ViewWrapper } from "./style";
import IconArrowLeft from "@/assets/svg/icon_arrow_left";
import IconArrow from "@/assets/svg/icon_arrow";

const ScrollView = memo((props) => {
  // 定义组件内部状态
  const [showRight, setShowRight] = useState(false);
  const [showLeft, setShowLeft] = useState(false);
  const [posIndex, setPosIndex] = useState(0);
  //  useRef 就算多次渲染也是唯一值,没必要使用useState,造成多次渲染
  const totalDispatchRef = useRef();
  // 组件渲染完毕,判断是否显示右侧的按钮
  const scrollContentRef = useRef();
  // useffect在组件渲染完成后执行
  useEffect(() => {
    const scrollWidth = scrollContentRef.current.scrollWidth; //一共可以滚动的宽度
    const clientWidth = scrollContentRef.current.clientWidth; //本身占据的宽度
    const totalDistance = scrollWidth - clientWidth;
    totalDispatchRef.current = totalDistance;
    setShowRight(totalDistance > 0);
  }, [props.children]);
  //   事件处理的逻辑
  function controlClickHandle(isRight) {
    const newIndex = isRight ? posIndex + 1 : posIndex - 1;
    const newEl = scrollContentRef.current.children[newIndex];
    const newOffsetLeft = newEl.offsetLeft;
    scrollContentRef.current.style.transform = `translate(-${newOffsetLeft}px)`;
    setPosIndex(newIndex);
    setShowRight(totalDispatchRef.current > newOffsetLeft);
    setShowLeft(newOffsetLeft > 0);
  }
  return (
    <ViewWrapper>
      {showLeft && (
        <div className="control left" onClick={(e) => controlClickHandle(false)}>
          <IconArrowLeft />
        </div>
      )}
      {showRight && (
        <div className="control right" onClick={(e) => controlClickHandle(true)}>
          <IconArrow />
        </div>
      )}
      <div className="scroll">
        <div className="scroll-content" ref={scrollContentRef}>
          {props.children}
        </div>
      </div>
    </ViewWrapper>
  );
});
export default ScrollView;
import styled from "styled-components";

export const ViewWrapper = styled.div`
  position: relative;
  padding: 8px 0;
  .scroll {
    overflow: hidden;
    .scroll-content {
      display: flex;
      transition: transform 250ms ease;
    }
  }

  .control {
    position: absolute;
    z-index: 9;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    text-align: center;
    border-width: 2px;
    border-style: solid;
    border-color: #fff;
    background: #fff;
    box-shadow: 0 1px 1px 1px rgba(0, 0, 0, 0.14);
    cursor: pointer;

    &.left {
      left: 0px;
      top: 50%;
      transform: translate(-50%, -50%);
    }

    &.right {
      right: 0px;
      top: 50%;
      transform: translate(-50%, -50%);
    }
  }
`;
import PropTypes from "prop-types";
import React, { memo, useState } from "react";
import { TabsWrapper } from "./style";
import classNames from "classnames";
import ScrollView from "@/base-ui/scroll-view";

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

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

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

SectionTabs.propTypes = {
  tabNames: PropTypes.array,
};
export default SectionTabs;

效果:

image.png

image.png

首页-向往数据的请求和滚动展示

先看效果:

image.png

  • 网络请求
export function getHomeLongforData() {
  return zmRequest.get({
    url: "/home/longfor",
  });
}
  • redux数据管理
import { getHomeDiscountData, getHomeGoodPriceData, getHomeHighScoreData, getHomeHotRecommendData, getHomeLongforData } from "@/services";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
export const fetchHomeDataAction = createAsyncThunk("fetchdata", (payload, { dispatch }) => {
  try {

    getHomeLongforData().then(res=>{
      dispatch(changeLongforInfoAction(res))
    });

  } catch (error) {
    console.log("6666=> ", error);
  }
});

const homeSlice = createSlice({
  name: "home",
  initialState: {
    longforInfo: {},
  },
  reducers: {
    changeLongforInfoAction(state, { payload }) {
      state.longforInfo = payload;
    },
  }
});

export const {
  changeGoodPriceInfoAction,
  changeHighScoreInfoAction,
  changeDiscountInfoAction,
  changeRecommendInfoAction,
  changeLongforInfoAction
} = homeSlice.actions;

export default homeSlice.reducer;
  • 组件中获取redux中数据,并传递给子组件
  // 从redux获取数据
  const { goodPriceInfo, highScoreInfo, discountInfo, recommendInfo,longforInfo } = useSelector(
    (state) => ({
      goodPriceInfo: state.home.goodPriceInfo,
      highScoreInfo: state.home.highScoreInfo,
      discountInfo: state.home.discountInfo,
      recommendInfo: state.home.recommendInfo,
      longforInfo:state.home.longforInfo
    }),
    shallowEqual
  );
  
  { isEmptyO(longforInfo) && <HomeLongfor infoData={longforInfo} />}
import React, { memo } from "react";
import PropTypes from "prop-types";
import { LongforWrapper } from "./style";
import LongforItem from "@/components/longfor-item";
import SectionHeader from "@/components/section-header";
import ScrollView from "@/base-ui/scroll-view";

const HomeLongfor = memo((props) => {
  const { infoData } = props;
  return (
    <LongforWrapper>
      <SectionHeader title={infoData.title} subtitle={infoData.subtitle} />
      <div className="longfor-list">
        <ScrollView>
          {infoData.list?.map((item) => {
            return <LongforItem itemData={item} key={item} />;
          })}
        </ScrollView>
      </div>
    </LongforWrapper>
  );
});
HomeLongfor.propTypes = {
  infoData: PropTypes.object,
};
export default HomeLongfor;
import styled from "styled-components";

export const LongforWrapper = styled.div`
  margin-top: 30px;

  .longfor-list {
    display: flex;
  }
`;

子组件LongforItem:

import React, { memo } from "react";
import PropTypes from "prop-types";
import { ItemWrapper } from "./style";

const LongforItem = memo((props) => {
  const { itemData } = props;
  return (
    <ItemWrapper>
      <div className="inner">
        <img src={itemData.picture_url} alt="" className="cover" />
        <div className="bg-cover"></div>
        <div className="info">
          <div className="city">{itemData.city}</div>
          <div className="price">均价{itemData.price}</div>
        </div>
      </div>
    </ItemWrapper>
  );
});

LongforItem.propTypes = {
  itemData: PropTypes.object,
};
export default LongforItem;
import styled from "styled-components";

export const ItemWrapper = styled.div`
  width: 20%;
  flex-shrink: 0;

  .inner {
    padding: 8px;
    position: relative;
    overflow: hidden;

    .cover {
      width: 100%;
      border-radius: 5px;
    }

    .bg-cover {
      position: absolute;
      left: 8px;
      right: 8px;
      height: 60%;
      background-image: linear-gradient(-180deg, rgba(0, 0, 0, 0) 3%, rgb(0, 0, 0) 100%);
    }

    .info {
      width: 100%;
      position: absolute;
      text-align: center;
      bottom: 25px;
      left: -1px;
      color: #fff;
    }
  }
`;

首页-plus数据的请求和展示过程

export function getHomePlusData() {
  return zmRequest.get({
    url: "/home/plus",
  });
}
import {
  getHomeDiscountData,
  getHomeGoodPriceData,
  getHomeHighScoreData,
  getHomeHotRecommendData,
  getHomeLongforData,
  getHomePlusData,
} from "@/services";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
export const fetchHomeDataAction = createAsyncThunk("fetchdata", (payload, { dispatch }) => {
  try {
    getHomePlusData().then((res) => {
      dispatch(changePlusInfoAction(res));
    });
  } catch (error) {
    console.log("6666=> ", error);
  }
});
const homeSlice = createSlice({
  name: "home",
  initialState: {
    plusInfo: {},
  },
  reducers: {
    changePlusInfoAction(state, { payload }) {
      state.plusInfo = payload;
    },
  }
});

export const {
  changePlusInfoAction,
} = homeSlice.actions;
export default homeSlice.reducer;
  // 从redux获取数据
  const { goodPriceInfo, highScoreInfo, discountInfo, recommendInfo, longforInfo, plusInfo } = useSelector(
    (state) => ({
      goodPriceInfo: state.home.goodPriceInfo,
      highScoreInfo: state.home.highScoreInfo,
      discountInfo: state.home.discountInfo,
      recommendInfo: state.home.recommendInfo,
      longforInfo: state.home.longforInfo,
      plusInfo: state.home.plusInfo,
    }),
    shallowEqual
  );
  
{isEmptyO(plusInfo) && <HomeSectionV3 infoData={plusInfo} />}

HomeSectionV3:

import PropTypes from "prop-types";
import React, { memo } from "react";
import { SectionV3Wrapper } from "./style";
import SectionHeader from "@/components/section-header";
import RoomItem from "@/components/room-item";
import ScrollView from "@/base-ui/scroll-view";
import SectionFooter from "@/components/section-footer";

const HomeSectionV3 = memo((props) => {
  const { infoData } = props;
  return (
    <SectionV3Wrapper>
      <SectionHeader title={infoData.title} subtitle={infoData.subtitle} />
      <div className="room-list">
        <ScrollView>
          {infoData.list?.map((item) => {
            return <RoomItem itemData={item} itemWidth="20%" key={item.id} />;
          })}
        </ScrollView>
      </div>
      <SectionFooter name="plus" />
    </SectionV3Wrapper>
  );
});

HomeSectionV3.propTypes = {
  infoData: PropTypes.object,
};
export default HomeSectionV3;
import styled from "styled-components";
export const SectionV3Wrapper = styled.div`
  .room-list {
    margin: 0 -8px;
  }
`;

效果:

image.png

全部页面(entire)的跳转和整体思路

import PropTypes from "prop-types";
import React, { memo } from "react";
import { FooterWrapper } from "./style";
import IconArrow from "@/assets/svg/icon_arrow";
import { useNavigate } from "react-router-dom";

const SectionFooter = memo((props) => {
  const { name } = props;
  let showMsg = "显示全部";
  if (name) {
    showMsg = `显示更多${name}房源`;
  }

  //事件处理的逻辑
  const navigate = useNavigate();
  function moreClickHandle() {
    navigate("/entire");
  }

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

SectionFooter.propTypes = {
  name: PropTypes.string,
};
export default SectionFooter;
import React, { memo } from "react";
import { EntireWrapper } from "./style";

const Entire = memo(() => {
  return (
    <EntireWrapper>
      <div className="filter">filter-section</div>
      <div className="rooms">room-section</div>
      <div className="pagination">pagination-section</div>
    </EntireWrapper>
  );
});
export default Entire;

image.png