在Service中发送网络请求
修改我们的services/modules下的home.js文件
// services/modules/home.js
import hyRequest from "../request";
export function getHomeGoodPriceData(){
return hyRequest.get({
url:"/home/goodprice"
})
}
export function getHomeHighScoreData(){
return hyRequest.get({
url:"/home/highscore"
})
}
然后我们将发送的请求数据放到redux中统一管理,下面就来将数据存储到redux中。
管理数据
1.修改我们的 store/modules/home.js
// store/modules/home.js
import { createSlice,createAsyncThunk } from "@reduxjs/toolkit"
import { getHomeGoodPriceData,getHomeHighScoreData } from "@/services"
export const fetchHomeDataAction = createAsyncThunk("fetchdata",(payload,{dispatch})=>{
getHomeGoodPriceData().then(res=>{
dispatch(changeGoodPriceInfoAction(res))
})
getHomeHighScoreData().then(res => {
dispatch(changeHighScoreInfoAction(res))
})
});
const homeSlice = createSlice({
name:"home",
initialState:{
goodPriceInfo:{},
highScoreInfo:{}
},
reducers:{
changeGoodPriceInfoAction(state,{payload}){
state.goodPriceInfo = payload
},
changeHighScoreInfoAction(state,{payload}){
state.highScoreInfo = payload;
}
},
})
export const { changeGoodPriceInfoAction,changeHighScoreInfoAction } = homeSlice.actions
export default homeSlice.reducer
然后就是修改我们的首页了。
修改首页
// views/home/index.jsx
import React,{memo, useEffect} from "react";
import { shallowEqual,useDispatch,useSelector } from "react-redux";
import HomeBanner from "./c-cpns/home-banner";
import { fetchHomeDataAction } from "@/store/modules/home";
import { HomeWrapper } from './style'
import HomeSectionV1 from "./c-cpns/home-section-v1";
const Home = memo(()=>{
// 从redux中取数据
const { goodPriceInfo,highScoreInfo } = useSelector((state)=>({
goodPriceInfo:state.home.goodPriceInfo,
highScoreInfo:state.home.highScoreInfo
}),shallowEqual)
// 派发异步的事件:发送网络请求
const dispatch = useDispatch();
useEffect(()=>{
dispatch(fetchHomeDataAction())
},[dispatch]);
return (
<HomeWrapper>
<HomeBanner></HomeBanner>
<div className="content">
<HomeSectionV1 infoData={goodPriceInfo}></HomeSectionV1>
<HomeSectionV1 infoData={highScoreInfo}></HomeSectionV1>
{/* <div className="good-price">
<SectionHeader title={goodPriceInfo.title}></SectionHeader>
<SectionRooms roomList={goodPriceInfo.list}></SectionRooms>
</div>
<div className="high-score">
<SectionHeader title={highScoreInfo.title} subtitle={highScoreInfo.subtitle}></SectionHeader>
<SectionRooms roomList={highScoreInfo.list}></SectionRooms>
</div> */}
</div>
</HomeWrapper>
)
})
export default Home
然后呢,然后引入我们的组件,由于这里是引入了两级,这里我们写两个栏目。
home-section-v1模块
// views/c-cpns/home-section-v1/index.jsx
import SectionHeader from "@/components/section-header";
import SectionRooms from '@/components/section-rooms';
import ProTypes from "prop-types";
import { memo } from "react";
import { SectionV1Wrapper } from "./style";
const HomeSectionV1 = memo((props)=>{
const { infoData } = props
return (
<SectionV1Wrapper>
<SectionHeader title={infoData.title} subtitle={infoData.subtitle}></SectionHeader>
<SectionRooms roomList={infoData.list}></SectionRooms>
</SectionV1Wrapper>
)
})
HomeSectionV1.prototype = {
infoData:ProTypes.object
}
export default HomeSectionV1
// views/c-cpns/home-section-v1/style.js
import styled from "styled-components";
export const SectionV1Wrapper = styled.div`
margin-top:30px;
`
总结
为了我们减少重复代码,我们将两个模块重新做了封装,更加优化了我们的首页,使得代码更少,更容易阅读。
选项卡
接下来就开始写我们的选项卡。具体代码如下:
发送网络请求
我们第一步需要发送网络请求,代码如下:
// services/modules/home.js
export function getHomeDiscountData(){
return hyRequest.get({
url:"/home/discount"
})
}
第二步,我们需要把数据存储在redux中。
// store/modules/home.js
import { createSlice,createAsyncThunk } from "@reduxjs/toolkit"
import { getHomeGoodPriceData,getHomeHighScoreData,getHomeDiscountData } from "@/services"
export const fetchHomeDataAction = createAsyncThunk("fetchdata",(payload,{dispatch})=>{
getHomeGoodPriceData().then(res=>{
dispatch(changeGoodPriceInfoAction(res))
})
getHomeHighScoreData().then(res => {
dispatch(changeHighScoreInfoAction(res))
})
getHomeDiscountData().then(res=>{
dispatch(changeDiscountInfoAction(res))
})
});
const homeSlice = createSlice({
name:"home",
initialState:{
goodPriceInfo:{},
highScoreInfo:{},
discountInfo:{}
},
reducers:{
changeGoodPriceInfoAction(state,{payload}){
state.goodPriceInfo = payload
},
changeHighScoreInfoAction(state,{payload}){
state.highScoreInfo = payload;
},
changeDiscountInfoAction(state,{payload}){
state.discountInfo = payload;
}
},
// extraReducers:{
// [fetchHomeDataAction.fulfilled](state,{payload}){
// state.goodPriceInfo = payload
// }
// }
// extraReducers: (builder) => {
// builder
// .addCase(fetchHomeDataAction.pending, (state, action) => {
// console.log("fetchHomeDataAction pending");
// })
// .addCase(fetchHomeDataAction.fulfilled, (state, { payload }) => {
// console.log(payload);
// state.goodPriceInfo = payload
// })
// .addCase(fetchHomeDataAction.rejected, (state, action) => {
// console.log("fetchHomeDataAction rejected");
// });
// }
})
export const {
changeGoodPriceInfoAction,
changeHighScoreInfoAction,
changeDiscountInfoAction
} = homeSlice.actions
export default homeSlice.reducer
然后我们需要编写样式了。
编写样式
修改我们的views/home/index.jsx文件
// views/home/index.jsx
import React,{memo, useEffect} from "react";
import { shallowEqual,useDispatch,useSelector } from "react-redux";
import HomeBanner from "./c-cpns/home-banner";
import { fetchHomeDataAction } from "@/store/modules/home";
import { HomeWrapper } from './style'
import { isEmptyO } from "@/utils/is-empty-object";
import HomeSectionV1 from "./c-cpns/home-section-v1";
import HomeSectionV2 from "./c-cpns/home-section-v2";
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())
},[dispatch]);
return (
<HomeWrapper>
<HomeBanner></HomeBanner>
<div className="content">
{isEmptyO(discountInfo) && <HomeSectionV2 infoData={discountInfo}></HomeSectionV2>}
{isEmptyO(goodPriceInfo) && <HomeSectionV1 infoData={goodPriceInfo}></HomeSectionV1>}
{isEmptyO(highScoreInfo) && <HomeSectionV1 infoData={highScoreInfo}></HomeSectionV1>}
</div>
</HomeWrapper>
)
})
export default Home
由于我们封装了个HomeSectionV2的组件,所以我们来编写,文件位置和HomeSectionV1组件是一样的,下面是代码。
// views/home/c-cpns/home-section-v2/index.jsx
import PropTypes from 'prop-types'
import React, { memo, useState, useCallback } from 'react'
import SectionHeader from '@/components/section-header'
import SectionRooms from '@/components/section-rooms'
import SectionTabs from '@/components/section-tabs'
import { SectionV2Wrapper } from './style'
import SectionFooter from '@/components/section-footer'
const HomeSectionV2 = memo((props) => {
/** 从props获取数据 */
const { infoData } = props
/** 定义内部的state */
const initialName = Object.keys(infoData.dest_list)[0]
const [name, setName] = useState(initialName)
const tabNames = infoData.dest_address?.map(item => item.name);
/** 事件处理函数 */
const tabClickHandle = useCallback(function (index, name) {
setName(name)
}, [])
return (
<SectionV2Wrapper>
<SectionHeader title={infoData.title} subtitle={infoData.subtitle}/>
<SectionTabs tabNames={tabNames} tabClick={tabClickHandle}/>
<SectionRooms roomList={infoData.dest_list?.[name]} itemWidth="33.33333%" />
<SectionFooter name={name}/>
</SectionV2Wrapper>
)
})
HomeSectionV2.propTypes = {
infoData: PropTypes.object
}
export default HomeSectionV2
下面是样式文件:
// views/home/c-cpns/home-section-v2/style.js
import styled from "styled-components";
export const SectionV2Wrapper = styled.div`
margin-top: 30px;
`
由于我们修改了SectionRooms组件的宽度,改为传参形式,所以我把SectionRooms组件的内容也给到大家:
// components/section-rooms/index.jsx
import PropTypes from 'prop-types'
import React, { memo } from 'react'
import RoomItem from '../room-item'
import { RoomsWrapper } from './style'
const SectionRooms = memo((props) => {
const { roomList = [], itemWidth } = props
return (
<RoomsWrapper>
{
roomList.slice(0, 8)?.map(item => {
return <RoomItem itemData={item} itemWidth={itemWidth} key={item.id}/>
})
}
</RoomsWrapper>
)
})
SectionRooms.propTypes = {
roomList: PropTypes.array,
}
export default SectionRooms
他的style样式文件如下:
// components/section-rooms/style.js
import styled from "styled-components";
export const RoomsWrapper = styled.div`
display:flex;
flex-wrap:wrap;
margin:0 -8px;
`
由于我们的sectionRooms参数修改的宽度内容传递到了RoomItem组件中,所以我把RoomItem组件中的内容也给到大家,如下:
// components/room-item/index.jsx
import PropTypes from "prop-types"
import React,{ memo } from "react"
import { Rating } from "@mui/material"
import { ItemWrapper } from "./style"
const RoomItem = memo((props)=>{
const { itemData,itemWidth=""} = props
return (
<ItemWrapper verifycolor={itemData?.verify_info?.text_color || "#39576a"} itemwidth={itemWidth}>
<div className="inner">
<div className="cover">
<img src={itemData.picture_url} alt="" />
</div>
<div className="desc">
{itemData.verify_info.messages.join(" · ")}
</div>
<div className="name">{itemData.name}</div>
<div className="price">¥{itemData.price}/晚</div>
</div>
{/* 评分 */}
<div className="bottom">
<Rating value={itemData.star_rating ?? 5}
precision={0.1}
readOnly
sx={{fontSize:"12px",color:"#00848A",marginRight:"-1px"}}
></Rating>
<span className="count">{itemData.reviews_count}</span>
{
itemData.bottom_info && <span className="extra">
·{itemData.bottom_info?.content}
</span>
}
</div>
</ItemWrapper>
)
})
RoomItem.propTypes = {
itemData: PropTypes.object
}
export default RoomItem
roomItem的样式内容,如下:
import styled from "styled-components";
export const ItemWrapper = styled.div`
flex-shrink: 0;
box-sizing: border-box;
width: ${props => props.itemwidth};
padding: 8px;
.inner {
width: 100%;
}
.cover {
position: relative;
box-sizing: border-box;
padding: 66.66% 8px 0;
border-radius: 3px;
overflow: hidden;
img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
}
.desc {
margin: 10px 0 5px;
font-size: 12px;
font-weight: 700;
color: ${props => props.verifyColor};
}
.name {
font-size: 16px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.price {
margin: 8px 0;
}
.bottom {
display: flex;
align-items: center;
font-size: 12px;
font-weight: 600;
color: ${props => props.theme.text.primaryColor};
.count {
margin: 0 2px 0 4px;
}
.MuiRating-decimal {
margin-right: -2px;
}
}
`
然后,我们发现home-section-v2中还使用了SectionFooter组件,所以我将section-footer的内容也给到大家。
section-footer
// components/section-footer/index.jsx
import IconMoreArrow from '@/assets/svg/icon-more-arrow';
import PropTypes from 'prop-types';
import React,{memo} from 'react';
import { useNavigate } from 'react-router-dom';
import { FooterWrapper } from './style'
const SectionFooter = memo((props)=>{
const { name } = props;
let showMessage = "显示全部";
if(name){
showMessage = `显示更多${name}房源`
}
// 事件处理的逻辑
const navigate = useNavigate();
function moreClickHandle(){
navigate("/entire")
}
return (
<FooterWrapper color={name ? "#00848A" : "#000"}>
<div className='info' onClick={moreClickHandle}>
<span className='text'>{showMessage}</span>
<IconMoreArrow></IconMoreArrow>
</div>
</FooterWrapper>
)
})
SectionFooter.propTypes = {
name:PropTypes.string
}
export default SectionFooter
下面是样式文件:
// components/section-footer/style.js
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;
}
}
`
好了,到此我们的热门目的地就搞好了,接下来我们就继续开发其他功能。
编写精彩之地模块,能想去模块,Plus房源模块
和之前开发流程一样,都是发送接口,然后将数据保存到redux中,然后再分模块拆分组件,将数据传入组件,这里就不作过多解释了。代码如下:
// views/home/index.jsx
import React,{memo, useEffect} from "react";
import { shallowEqual,useDispatch,useSelector } from "react-redux";
import HomeBanner from "./c-cpns/home-banner";
import { fetchHomeDataAction } from "@/store/modules/home";
import { HomeWrapper } from './style'
import { isEmptyO } from "@/utils/is-empty-object";
import HomeSectionV1 from "./c-cpns/home-section-v1";
import HomeSectionV2 from "./c-cpns/home-section-v2";
import HomeLongfor from './c-cpns/home-longfor';
import HomeSectionV3 from "./c-cpns/home-section-v3";
const Home = memo(()=>{
// 从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)
// 派发异步的事件:发送网络请求
const dispatch = useDispatch();
useEffect(()=>{
dispatch(fetchHomeDataAction())
},[dispatch]);
return (
<HomeWrapper>
<HomeBanner></HomeBanner>
<div className="content">
{isEmptyO(discountInfo) && <HomeSectionV2 infoData={discountInfo}></HomeSectionV2>}
{/* 精彩之地 */}
{isEmptyO(recommendInfo) && <HomeSectionV2 infoData={recommendInfo}></HomeSectionV2> }
{/* 能想去 */}
{isEmptyO(longforInfo) && <HomeLongfor infoData={longforInfo}></HomeLongfor>}
{isEmptyO(goodPriceInfo) && <HomeSectionV1 infoData={goodPriceInfo}></HomeSectionV1>}
{isEmptyO(highScoreInfo) && <HomeSectionV1 infoData={highScoreInfo}></HomeSectionV1>}
{/* Plus房源 */}
{isEmptyO(plusInfo) && <HomeSectionV3 infoData={plusInfo}></HomeSectionV3>}
</div>
</HomeWrapper>
)
})
export default Home
组件文件:
HomeLongfor
// views/home/c-cpns/home-longfor/index.jsx
import ScrollView from "@/base-ui/scroll-view"
import LongforItem from "@/components/longfor-item";
import SectionHeader from "@/components/section-header";
import ProTypes from 'prop-types';
import React,{ memo } from "react"
import { LongforWrapper } from "./style"
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.city}/>
})
}
</ScrollView>
</div>
</LongforWrapper>
)
})
HomeLongfor.propTypes = {
infoData:ProTypes.object
}
export default HomeLongfor
// views/home/c-cpns/home-longfor/style.js
import styled from "styled-components";
export const LongforWrapper = styled.div`
margin-top: 30px;
.longfor-list {
display: flex;
margin: 0 -8px;
}
`
HomeSectionV3组件
//views/c-cnps/home-section-v3/index.jsx
import PropTypes from 'prop-types'
import React, { memo } from 'react'
import SectionHeader from "@/components/section-header";
import { SectionV3Wrapper } from './style';
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}></SectionHeader>
<div className="room-list">
<ScrollView>
{
infoData.list.map(item=>{
return <RoomItem itemData={item} itemWidth="20%" key={item.id}></RoomItem>
})
}
</ScrollView>
</div>
<SectionFooter name="plus"></SectionFooter>
</SectionV3Wrapper>
)
})
HomeSectionV3.propTypes = {
infoData:PropTypes.object
}
export default HomeSectionV3
//views/c-cnps/home-section-v3/style.js
import styled from 'styled-components';
export const SectionV3Wrapper = styled.div`
.room-list{
margin:0 -8px;
}
`
service
// services/modules/home.js
import hyRequest from "../request";
export function getHomeGoodPriceData(){
return hyRequest.get({
url:"/home/goodprice"
})
}
export function getHomeHighScoreData() {
return hyRequest.get({
url: "/home/highscore"
})
}
export function getHomeDiscountData(){
return hyRequest.get({
url:"/home/discount"
})
}
export function getHomeHotRecommendData() {
return hyRequest.get({
url: "/home/hotrecommenddest"
})
}
export function getHomeLongforData(){
return hyRequest.get({
url:"/home/longfor"
})
}
export function getHomePlusData(){
return hyRequest.get({
url:"/home/plus"
})
}
store
// store/modules/home.js
import { createSlice,createAsyncThunk } from "@reduxjs/toolkit"
import { getHomeGoodPriceData,getHomeHighScoreData,getHomeDiscountData,getHomeHotRecommendData,getHomeLongforData,getHomePlusData } from "@/services"
export const fetchHomeDataAction = createAsyncThunk("fetchdata",(payload,{dispatch})=>{
getHomeGoodPriceData().then(res=>{
dispatch(changeGoodPriceInfoAction(res))
})
getHomeHighScoreData().then(res => {
dispatch(changeHighScoreInfoAction(res))
})
getHomeDiscountData().then(res=>{
dispatch(changeDiscountInfoAction(res))
})
getHomeHotRecommendData().then(res=>{
dispatch(changeRecommendInfoAction(res));
})
getHomeLongforData().then(res=>{
dispatch(changeLongforInfoAction(res))
})
getHomePlusData().then(res=>{
dispatch(changePlusInfoAction(res))
})
});
const homeSlice = createSlice({
name:"home",
initialState:{
goodPriceInfo:{},
highScoreInfo:{},
discountInfo:{},
recommendInfo:{},
longforInfo:{},
plusInfo:{}
},
reducers:{
changeGoodPriceInfoAction(state,{payload}){
state.goodPriceInfo = payload
},
changeHighScoreInfoAction(state,{payload}){
state.highScoreInfo = payload;
},
changeDiscountInfoAction(state,{payload}){
state.discountInfo = payload;
},
changeRecommendInfoAction(state,{payload}){
state.recommendInfo = payload
},
changeLongforInfoAction(state,{payload}){
state.longforInfo = payload
},
changePlusInfoAction(state,{payload}){
state.plusInfo = payload
}
},
// extraReducers:{
// [fetchHomeDataAction.fulfilled](state,{payload}){
// state.goodPriceInfo = payload
// }
// }
// extraReducers: (builder) => {
// builder
// .addCase(fetchHomeDataAction.pending, (state, action) => {
// console.log("fetchHomeDataAction pending");
// })
// .addCase(fetchHomeDataAction.fulfilled, (state, { payload }) => {
// console.log(payload);
// state.goodPriceInfo = payload
// })
// .addCase(fetchHomeDataAction.rejected, (state, action) => {
// console.log("fetchHomeDataAction rejected");
// });
// }
})
export const {
changeGoodPriceInfoAction,
changeHighScoreInfoAction,
changeDiscountInfoAction,
changeRecommendInfoAction,
changeLongforInfoAction,
changePlusInfoAction
} = homeSlice.actions
export default homeSlice.reducer
感谢大家观看,我们下次见
十天看一部剧,还可以吧
@梦不见的梦 行,谢谢提醒,我优化一下
网站的速度有待提升,每次打开都要转半天还进不来呢
@React实战爱彼迎项目(二) - 程序员鸡皮 哪里有问题了,报错了吗?
@Teacher Du 那是怕你们毕不了业,我大学那会儿给小礼品
我们大学那会,献血还给学分~
@ab 我想去学网安,比如网警,但分也贼高😕
@夜 加油,你一样也可以成为程序员的,需要学习资料可以V我
佬发布的好多技术文章似乎我只能评论这篇,真正的程序员!!哇塞 我也想去献血,过两年就成年了可以去献血了