react项目:仿airbnb首页(二)部分頁面搭建

312 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

Header

Snipaste_2022-12-08_09-00-39

只有1个Header组件 通过控制样式来决定不同页面的Header

写css in js 可以样式嵌套 在index中用HeaderWrapper包裹组件

export const HeaderWrapper = styled.div`
  display: flex;
  align-items: center;
  height: 80px;
  border-bottom: 1px solid #eee;
`;

模版字符串就相当于调用函数

让中间的居中:大的盒子 display: flex; 左右两边盒子为flex:1

svg可以让图片所占用的内存变小

svg引入的方式 => 封装成组件,即把svg放在独立jsx组件中,在另一个文件中引入组件即可

svg中currentColor就是离它最近的父级的颜色

styleStrToObject可以将style中字符串转成对象(在谷歌中用英语搜索style to Object)

svg图片使用到了styleStrToObject 将网页复制下来的svg中字符串样式转为对象

  <svg
      viewBox="0 0 32 32"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      role="presentation"
      focusable="false"
      style={styleStrToObject(
        "display: block; height: 25px; width: 25px; fill: currentcolor;"
      )}
    >
      <path></path>
    </svg>

项目的主题配置

在index.js中使用

   <ThemeProvider theme={theme}>
        <HashRouter>
          <App/>
        </HashRouter>
      </ThemeProvider>

用styled-component来管理样式主题 用props读取主题颜色

import styled from "styled-components";
.desc {
    margin: 10px 0 5px;
    font-size: 12px;
    font-weight: 700;
    color: ${(props) => props.verifyColor};
  }

将阴影动画抽取用Mixins

在theme中

  mixin: {
    boxShadow: ` 
    transition: box-shadow 200ms ease;
    &:hover {
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18); //x y 扩展 透明度
    }
    `,
  },

需要加阴影的元素的css中:

 ${(props) => props.theme.mixin.boxShadow}

点击出现panel栏

Snipaste_2022-12-08_09-10-50

一定是绝对定位 高度由内部元素撑起,不设置固定值

切换显示、隐藏:showPanel值 再写事件处理函数

隐藏:监听点击 是在useEffect 监听window的点击,这样在页面其他地方仍然可以交互;在捕获阶段

  useEffect(() => {
    function windowHandleClick() {
      setShowPanel(false);
    }

    // 最后1个参数true 是为了做事件捕获 防止事件冒泡而将showPanel设置为false
    window.addEventListener("click", windowHandleClick, true);
    return () => {
      window.removeEventListener("click", windowHandleClick, true);
    };
  }, []);

主页

banner

在react中引入图片的方法:

  1. import
  2. 通过require传入src或url 因为webpack打包后图片的路径不再是原来
   background:url(${require("图片路径")})

高性价比模块

直接子元素 > 避免类名重复导致样式被错误应用

export const HomeWrapper = styled.div`
  > .content {
    width: 1032px;
    margin: 0 auto;
  }
`;
网络请求获取数据的过程

代码在:

airbnb\src\store\modules\home.js

airbnb\src\store\index.js

修改state要通过reducer

reducers: {
    changeGoodPriceInfoAction(state, { payload }) {
      state.goodPriceInfo = payload;
    },

redux中(store文件夹)通过createAsyncThunk调用网络请求方法得到数据

export const fetchHomeDataAction = createAsyncThunk(
  "fetchdata",
  (payload, { dispatch }) => {
    getHomeGoodPriceData().then((res) => {
      dispatch(changeGoodPriceInfoAction(res));
    });}

在组件中的useEffect通过dispatch派发异步事件 通过useSelector获取数据

 const { goodPriceInfo } = useSelector(
    (state) => ({
      goodPriceInfo: state.home.goodPriceInfo,
    }),
    shallowEqual
  );

const dispatch = useDispatch();
  useEffect(() => {
    dispatch(fetchHomeDataAction("xxxx"));
  }, [dispatch]);
数据展示

小的组件放到components

rmcp会增加propTypes对传入的参数做类型验证

footer部分

直接看代码

room-item组件

代码:airbnb\src\components\room-item\style.js

总的是弹性布局,可以换行

第一次没有数据 就给map之前的数据后面加上?

只展示前8条数据:用slice

**间距:**一行排4个 每个width是25% border-box 然后间距用padding撑开 最右和最左的左边距和右边距在最外层的盒子用margin:0 -数值 对齐

除了padding之外做一个inner div

先搭html结构展示数据

服务器给的图片的比例不一样 宽度100% 高度就会不一致

使用padding让高度是宽度的2/3 图片绝对定位

 .cover {
    position: relative;
    box-sizing: border-box;
    padding: 66.6% 8px 0;
    border-radius: 3px;
    overflow: hidden;

    img {
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
    }
  }

文本的颜色由服务器决定 服务器会返回颜色:

给ItemWrapper传入服务器返回的数据

Antd和MUI的集成

MUI的集成

css是用css in js 要用styled-component\emotion编译

如果用styled-component需要在webpack中配置别名 要同时安装emotion,因为底层MUI的依赖了emotion(卸载emotion的时候,nodemodule里面还有这个包)

npm install @mui/material @mui/styled-engine-sc styled-components
npm install @mui/material @emotion/react @emotion/styled

引入组件才能用

Antd的集成

  1. 安装antd

如果要写less就要在craco.config中配置插件选项

 plugins: [
    {
      plugin: CracoLessPlugin,
      options: {
        lessLoaderOptions: {
          lessOptions: {
            modifyVars: {},
            javascriptEnabled: true,
          },
        },
      },
    },
  ],

  1. 在index中引入
   import "antd/dist/antd.less";

做五星好评:

自定义大小和颜色、间距

高评分模块

在service的home模块里封装网络请求

在store的home里发生请求,在AsyncThunk里发送网络请求,有2种方法

  1. 如果用await 就会等到一个请求完成后再请求另一个
  2. 用Promise,然后将store解构出dispatch,然后派发action

可以用第二种方法Promise.then的方法拿到数据后dispatch

export const fetchHomeDataAction = createAsyncThunk(
  "fetchdata",
  (payload, { dispatch }) => {
    getHomeGoodPriceData().then((res) => {
      dispatch(changeGoodPriceInfoAction(res));
    });
    getHomeHighScoreData().then((res) => {
      dispatch(changeHighScoreInfoAction(res));
    });
  }

在view的home里拿到数据并展示

选项卡切换

点击每个选项卡就显示对应的页面

整理数据 过滤出有对应id的数据

在service的home模块里封装网络请求

在store的home里的AsyncThunk里发送网络请求,

因为sectionRoom封装成组件,宽度由外界决定,所以2个地方不能写固定值 动态传入宽度:

在index中传入 然后通过props拿到 在sectionRoom拿到width再传递给RoomItem,RoomItem通过props接收,再传递给ItemWrapper(样式组件) 在ItemWrapper中通过props拿到itemWidth

tabs的封装和切换

封装SectionTabs组件

数据转换 :在home的index中拿到每个item的name

展示数据 html和css flex-basis

内部切换:监听点击 currentIndex记录哪个被点击

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

  function itemClickHandle(index, item) {
    setCurrentIndex(index);
    tabClick(index, item);
  }

  return (
    <TabsWrapper>
      <ScrollView>
        {tabNames.map((item, index) => {
          return (
            <div
              key={index}
              //安装classnames 样式里有active就添加背景色
              className={classNames("item", { active: index === currentIndex })}
              onClick={(e) => itemClickHandle(index, item)}
            >
              {item}
            </div>
          );
        })}
         </ScrollView>
    </TabsWrapper>

将内部切换的事件传递给外部 子传父:

父给子传递函数 在Home的index中给SectionTabs传函数 用useCallback包裹

将name定义为变量

Snipaste_2022-12-08_10-06-45