【大众点评】—— 首页开发

前言:正在学习react大众点评项目课程,学习react、redux、react-router构建项目。


【大众点评】—— 首页开发

 

一、页面分析和组件划分

【大众点评】—— 首页开发

 

二、组件开发

走马灯效果 react-slick

npm install react-slick --save
//public ->index.html
<link rel="stylesheet" type="text/css" charset="UTF-8" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css" />
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css" />

class命名规范:BEM命名规范  

<ins></ins> 被插入的文本
<del></del> 被删除的文本

猜你喜欢 加载更多的效果

【大众点评】—— 首页开发  

//LikeList->index.js
import React, { Component } from "react";
import LikeItem from "../LikeItem";
import Loading from "../../../../components/Loading";
import "./style.css";

class LikeList extends Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
    this.removeListener = false;
  }

  render() {
    const { data, pageCount } = this.props;
    return (
      <div ref={this.myRef} className="likeList">
        <div className="likeList__header">猜你喜欢</div>
        <div className="likeList__list">
          {data.map((item, index) => {
            return <LikeItem key={index} data={item} />;
          })}
        </div>
        {pageCount < 3 ? (
          <Loading />
        ) : (
          <a className="likeList__viewAll">查看更多</a>
        )}
      </div>
    );
  }

  componentDidMount() {
    if(this.props.pageCount < 3 ) {
      document.addEventListener("scroll", this.handleScroll);
    }else {
      this.removeListener = true;
    }
    if(this.props.pageCount === 0) {
      this.props.fetchData();
    }
  }

  componentDidUpdate() {
    //使用加载更多功能2次后解除绑定,不再使用scroll监听事件
    if (this.props.pageCount >= 3 && !this.removeListener) {
      document.removeEventListener("scroll", this.handleScroll);
      this.removeListener = true;
    }
  }

  componentWillUnmount() {
    //如果没有移除scroll监听事件。需要手动移除
    if (!this.removeListener) {
      document.removeEventListener("scroll", this.handleScroll);
    }
  }

  // 处理屏幕滚动事件,实现加载更多的效果
  handleScroll = () => {
    //获取页面滚动的距离 (兼容不同的浏览器)
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    //获取屏幕可视区域的高度
    const screenHeight = document.documentElement.clientHeight;
    //获取LikeList页面距离顶部的距离、LikeList页面高度
    const likeListTop = this.myRef.current.offsetTop;
    const likeListHeight = this.myRef.current.offsetHeight;
    if (scrollTop >= likeListHeight + likeListTop - screenHeight) {
      this.props.fetchData();
    }
  };
}

export default LikeList;
//components->Loading->index.js
import React, { Component } from 'react';
import "./style.css"

class Loading extends Component {
  render() {
    return (
      <div className="loading">
        <div className="loading__img"/>
        <span>正在加载...</span>
      </div>
    );
  }
}

export default Loading;

 

三、redux状态管理 定义首页所需状态  

    Redux模块设计  

1.设计state

redux->modules->home.js

const initialState = {
    //猜你喜欢相关state
    likes: {
        isFetching: false, //正在获取数据
        pageCount: 0, //分页加载
        ids: [], //猜你喜欢的每一个产品的id
    },
    //获取超值特惠相关state
    discounts: {
        isFetching: false,
        ids: [],
    }
}

2.定义Actions  

import {get} from "../../utils/request"
import url from "../../utils/url"
import { FETCH_DATA } from "../middleware/api"
import { schema } from "./entities/products"

//请求参数使用到的常量对象
export const params = {
    PATH_LIKES:'likes',
    PATH_DISCOUNTS: 'discounts',
    PAGE_SIZE_LIKES: 5,
    PAGE_SIZE_DISCOUNTS: 3
}

export const types = {
    //获取猜你喜欢请求
    FETCH_LIKES_REQUEST: "HOME/FETCH_LIKES_REQUEST",
    //获取猜你喜欢请求成功
    FETCH_LIKES_SUCCESS: "HOME/FETCH_LIKES_SUCCESS",
    //获取猜你喜欢请求失败
    FETCH_LIKES_FAILURE: "HOME/FETCH_LIKES_FAILURE",
    //获取超值特惠请求
    FETCH_DISCOUNTS_REQUEST: "HOME/FETCH_DISCOUNTS_REQUEST",
    //获取超值特惠请求成功
    FETCH_DISCOUNTS_SUCCESS: "HOME/FETCH_DISCOUNTS_SUCCESS",
    //获取超值特惠请求失败
    FETCH_DISCOUNTS_FAILURE: "HOME/FETCH_DISCOUNTS_FAILURE",
}

//加载的异步actions
export const actions = {
    //加载猜你喜欢的数据
    loadLikes: () => {
        //返回一个函数,通过redux-thunk中间件进行处理
        return (dispatch, getState) => {
            const {pageCount} = getState().home.likes;
            const rowIndex = pageCount * params.PAGE_SIZE_LIKES;
            const endpoint = url.getProductList(params.PATH_LIKES, rowIndex, params.PAGE_SIZE_LIKES)
            //发送Action creator
            return dispatch(fetchLikes(endpoint))
        }
    },
    //加载特惠商品
    loadDiscounts: () => {
        return (dispatch, getState) => {
            const endpoint = url.getProductList(params.PATH_DISCOUNTS, 0, params.PAGE_SIZE_DISCOUNTS)
            return dispatch(fetchDiscounts(endpoint))
        }
    }
}

//定义action creator
const fetchLikes = (endpoint) => ({
    [FETCH_DATA]: {
        types: [
            types.FETCH_LIKES_REQUEST,
            types.FETCH_LIKES_SUCCESS,
            types.FETCH_LIKES_FAILURE
        ],
        endpoint,
        schema
    }
})
const fetchDiscounts = (endpoint) => ({
    [FETCH_DATA]: {
        types: [
            types.FETCH_DISCOUNTS_REQUEST,
            types.FETCH_DISCOUNTS_SUCCESS,
            types.FETCH_DISCOUNTS_FAILURE
        ],
        endpoint,
        schema
    }
})

utils->url.js  

export default {
    //额外的一个类型参数:path ->rest常用的区分不同资源的命名方式
    //获取的是第几条记录 ,每页获取多少条记录
    getProductList: (path, rowIndex, pageSize) =>
    `/mock/products/${path}.json?rowIndex=${rowIndex}&pageSize=${pageSize}`
}

3.定义Reducers(处理相应的action types)  

import { combineReducers } from "redux";

//定义reducer 处理猜你喜欢的action type
const likes = (state = initialState.likes, action) => {
    switch(action.type) {
        case types.FETCH_LIKES_REQUEST:
           return {...state, isFetching: true};
        case types.FETCH_LIKES_SUCCESS:
           return {
               ...state,
               isFetching: false,
               pageCount: state.pageCount+1,
               ids: state.ids.concat(action.response.ids)
           };
        case types.FETCH_LIKES_FAILURE:
           return {...state, isFetching: false}
        default:
            return state;
    }
}

//定义reducer 处理特惠商品的action type
const discounts = (state = initialState.discounts, action) => {
    switch(action.type) {
        case types.FETCH_DISCOUNTS_REQUEST:
            return {...state, isFetching: true};
        case types.FETCH_DISCOUNTS_SUCCESS:
            return {
                ...state,
                isFetching: false,
                ids: state.ids.concat(action.response.ids)
            };
        case types.FETCH_DISCOUNTS_FAILURE:
            return {...state, isFetching: false}
        default:
            return state;
    }
}

const reducer = combineReducers({
    discounts,
    likes
})

export default reducer;

    首页组件连接Redux  

  • Redux中定义view层组件需要的Selectors函数,从Redux中获取组件需要的相关状态
  • 容器组件中定义mapStateToProps、mapDispatchToProps,用于在Redux中将Redux的state转化为组件的props,将Redux的action也转化为组件的props的相关方法
  • 组件使用相关的props进行数据的展示和数据的更改

1.定义Selector

redux->modules->home.js

//Selectors
//获取猜你喜欢的state
export const getLikes = state => {
    return state.home.likes.ids.map(id => {
        return state.entities.products[id]  //数组类型保证数据展示的有序性
    })
}
//获取特惠商品的state
export const getDiscounts = state => {
    return state.home.discounts.ids.map(id => {
        return state.entities.products[id]
    })
}
//猜你喜欢当前分页吗
export const getPageCountOfLikes = state => {
    return state.home.likes.pageCount
}

2.mapStateToProps、mapDispatchToProps  

//帮助使用props调用action,不是使用dispatch调用action
import { bindActionCreators} from 'redux'

//高阶组件(函数),完成组件和redux层的连接
import { connect } from 'react-redux'
……
//actions 和 selectors
import { actions as homeActions, getLikes, getDiscounts, getPageCountOfLikes} from '../../redux/modules/home'

class Home extends Component {
    render() {
        const {likes, discounts, pageCount} = this.props
        return (
            <div>
                <HomeHeader />
                <Banner />
                <Category />
                <Activity />
                <Headline />
                <Discount data={discounts} />
                <LikeList data={likes} pageCount={pageCount} fetchData={this.fetchMoreLikes}/>
                <Footer />
            </div>
        );
    }

    ComponentDidMount() {
        this.props.homeActions.loadDiscounts();
    }

    fetchMoreLikes = () => {
        this.props.homeActions.loadLikes()
    }
}

const mapStateToProps = (state, props) => {
    //返回在容器型组件中使用到的对象,通过selector获取到的属性都会挂载到props下
    return {
       likes: getLikes(state),
       discounts: getDiscounts(state),
       pageCount: getPageCountOfLikes(state)
    }
}

const mapDispatchToProps = dispatch => {
    return {
        homeActions: bindActionCreators(homeActions, dispatch)
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

3.组件使用  

不再在组件内维护dataSource和单独的state

Discount->index.js

const { data } = this.props;

LikeList->index.js  

const {data, pageCount} = this.props;

componentDidMount() {
    document.addEventListener("scroll", this.handleScroll);
    this.props.fetchData();
}

componentDidUpdate() {
    //使用加载更多功能2次后解除绑定,不再使用scroll监听事件
    if(this.props.pageCount >=3 && !this.removeListener) {
        document.removeEventListener("scroll", this.handleScroll);
        this.removeListener = true;
    }
}

    Redux作为数据缓存层的作用

redux->modules->home.js

//加载特惠商品
loadDiscounts: () => {
      return (dispatch, getState) => {
          //redux数据缓存层的使用
          const {ids} = getState().home.discounts;
          if(ids.length > 0){
              return null
          }
          const endpoint = url.getProductList(params.PATH_DISCOUNTS, 0, params.PAGE_SIZE_DISCOUNTS)
          return dispatch(fetchDiscounts(endpoint))
      }
} 

LikeList->index.js  

componentDidMount() {
     if(this.props.pageCount < 3){
            document.addEventListener("scroll", this.handleScroll);
     }else{
            this.removeListener = true;
     }

     if(this.props.pageCount === 0){
            this.props.fetchData();
     }
}

 

四、集成React Router  

Router基本结构定义

containers->App->index.js

import { BrowserRouter as Router, Route, Switch} from 'react-router-dom';

<Router>
   <Switch>
       <Route path="/" component={Home} />
   </Switch>
</Router> 

添加首页到其它页面的Link  

HomeHeader->index.js 

import { Link } from 'react-router-dom';

<Link to="/search" className="homeHeader__search">输入商户名、地点</Link>
<Link to='/user' className="homeHeader__self">
     <div className="homeHeader__portrait"></div>
</Link>

Discount->index.js  

import { Link } from 'react-router-dom';

<Link key={item.id} to={`/detail/${item.id}`} className='discount__item'>
     ……
</Link>

LikeItem->index.js  

import { Link } from 'react-router-dom';

<Link className="likeItem" to={`/detail/${id}`}>
     ……
</Link>

注:项目来自慕课网  

上一篇:Jquery ajax 、axios、Fetch 区别


下一篇:使用requests遇到的坑