前言:正在学习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>
注:项目来自慕课网