水平滚动组件封装
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;
效果:
首页-向往数据的请求和滚动展示
先看效果:
- 网络请求
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;
}
`;
效果:
全部页面(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;