程序员鸡皮

文章 分类 评论
61 3 9

站点介绍

一名PHP全栈程序员的日常......

React实战爱彼迎项目(三)

abzzp 2024-09-06 354 0条评论 前端 React

首页 / 正文
本站是作为记录一名北漂程序员编程学习以及日常的博客,欢迎添加微信BmzhbjzhB咨询交流......

发布于2024-07-04

在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

感谢大家观看,我们下次见

评论(0)

最新评论

  • abzzp

    十天看一部剧,还可以吧[[呲牙]]

  • ab

    @梦不见的梦 行,谢谢提醒,我优化一下

  • 梦不见的梦

    网站的速度有待提升,每次打开都要转半天还进不来呢

  • abzzp

    @React实战爱彼迎项目(二) - 程序员鸡皮 哪里有问题了,报错了吗?[[微笑]]

  • abzzp

    @Teacher Du 那是怕你们毕不了业,我大学那会儿给小礼品[[发呆]]

  • Teacher Du

    我们大学那会,献血还给学分~

  • @ab 我想去学网安,比如网警,但分也贼高😕

  • ab

    @夜 加油,你一样也可以成为程序员的,需要学习资料可以V我

  • 佬发布的好多技术文章似乎我只能评论这篇,真正的程序员!!哇塞 我也想去献血,过两年就成年了可以去献血了

日历

2025年01月

   1234
567891011
12131415161718
19202122232425
262728293031 

文章目录

推荐关键字: vue JavaScript React Golang 观后感 ES6 SEO 读后感

站点公告
本站是作为记录一名北漂程序员编程学习以及日常的博客,欢迎添加微信BmzhbjzhB咨询交流......
点击小铃铛关闭
配色方案