项目实战:首页开发之简书

app.js

import store from './store/';
import React,{ Component } from 'react';
import { Provider }from 'react-redux';
import header from './common/header';
import { BrowserRouter ,Route } from ' react-routter-dom';//引入路由
import Home from './pages/home';
import Detail from './pages/detail';


class App extends Component {
	render(){
		return (
			<Provider store={store}>
			//provider标签作用:store里数据都提供给了provider里所有组件
			//provider里东西很多时最好整个包一个<div>
				<div>
					<Header />
					<BrowserRouter>//也只能有一个children 也要整体包一个<div>
						<div>
							<Route path='/'  exact component={Home}></Route>
							<Route path='/detail'  exact component={Detail}></Route>
						</div>
				</BrowserRouter>
				</div>
			</ Provider >
				);
			}
	}
export default App;

home/index.js

import React, { Component }from 'react';
import { HomeWrapper,HomeLeft,HomeRight } from './style';
import Topic from './components/Topic';
import List from './components/Topic';
import Recommend from './components/Topic';
import Writer from './components/Topic';
import { connect } from 'react-redux';
import axios from 'axios';
import {BackTop} from'./style';
class Home extends Component {
	handleScrollTop(){
		window.scrollTo(0,0);
	}
	render(){
		return(
			<HomeWrapper>
				<HomeLeft>
					<img className="banner-img' src="url">
					<Topic />
					<List />
				</HomeLeft>
				<HomeRight>
					<Recommend />
					<writer />
				</HomeRight>
				{this.props.showScroll ?<BackTop onClick={this.handleScrollTop}></BackTop>:null}
				
			</HomeWrapper>
		)
	}
	componentDidMount(){//页面挂载完毕时开始执行
		this.props.changeHomeData();
		this.bindEvents();
}
componentWillUnmount(){//页面即将被销毁时
		//解绑事件
		window.removeEventListener('scroll',this.props.changeScrollTopShow}
}
bindEvents(){
	window.addEventListener('scroll',this.props.changeScrollTopShow}//监听屏幕滚动下拉多少距离
	
}
const mapStateToProps =(state)=>({
	showScroll:state.getIn(['home','showScroll'])
})
const mapDispatchToProps = (dispatch) => ({
	changeHomeData(action){
		axios.get('/api/home.json').them((res) => {
			const result =res.data.data;
			const action = {
				type:'change_home_data',
				topicList:result.topicList,
				articleList:result.articleList,
				recommendList:result.recommendList
			}
			dispatch(action);//所有小reducer都能接收到
		})
	}
	changeScrollTopShow(){
		if(document.documentElement.scrollTop >400)
		dispatch(actionCreators.toggleTopShow(true));
		else dispatch(actionCreators.toggleTopShow(false));
	}
	}
});

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

home/style.js
import styled from 'styled-components';
export const HomeWrapper = styled.div`
	width:960px;
	margin:0 auto;
	overflow:hidden;//溢出隐藏
`;
export const HomeLeft = styled.div`
	width:625px;
	margin-left:15px;
	padding-top:30px;
	float:left;
	.banner-img{
		width:625px;
		height:270px;
	}
`;
export const HomeRight = styled.div`
	width:280px;
	float:right;
`;
export const TopicWrapper = styled.div`
	overflow:hidden;//使浮动的内部高度自适应
	padding :20px 0 10px 0; //up right down left
	margin-left:-10px;
	border-bottom:1px solid #dcdcdc;
`;
export const TopicItem = styled.div`
	margin-left:18px;
	marigin-bottom:18px;
	padding-right:10px;
	float:left;
	height:32px;
	line-height:32px;
	background:#f7f7f7;
	font-size:14px;
	border:1px solid #dcdcdc;
	border-radius:4px;
	.topic-pic {
		width:32px;
		height:32px;
		display:block;
		float:left;
		margin-right:10px;
	}
`;
export const ListItem = styled.div`
	overflow:hidden;
	boder-bottom:1px solid #dcdcdc;
	padding :20px 0; //up  down 
	.pic {
		width:125px;
		height:100px;
		display:block;
		float:right;
		border-radius:10px;
	}
`;
export const ListInfo = styled.div`
	width:500px;
	float:left;
	.title{
		line-height:27px;
		font-size:18px;
		font-weight:bold;
		color:#333;
	}
	.desc {
		line-height:24px;
		font-size:13px;
		color:#999;
`;
export const RecommendWrapper =styled.div`
	margin:30px 0;
	width:280px;
`
export const RecommendItem= styled.div`
	width:280px;
	height:50px;
	background:url( ${(props) =>props.imgUrl});
	background-size:contain;
`
export const LoadMore = styled.div`
	width:100%
	height:40px;
	line-height:40px;
	background:#a5a5a5 ;
	border-radius:20px;
	color:#fff;
	text-align:center;
	margin:30px 0;
	cursor:pointer;
`
export const BackTop = styled.div`
	position:fixed;//fixed	生成绝对定位的元素,相对于浏览器窗口进行定位。
	width:60px;
	height:60px;
	line-height:60px;
	text-align:center;
	border:1px solid #ccc;
	right:30px;
	bottom:30px;
`

src/pages/home/components/Topic.js
import React, { Component }from 'react';
import { connect } from 'react-redux';
import { TopicWrapper,TopicItem } from '../style';
//由于home里项目比较小 我们把home各组件样式全部集中在pages/home/style.js
class Topic extends Component {
	render(){
		return(
			<TopicWrapper>
				{this.props.list.map((item) => {
				return (
					<TopicItem key={item.get('id')}>
						<img className='topic-pic'
						src={item.get('imgUrl')}>
						{item.get('title')}
					</TopicItem>
					)
				}}
			</TopicWrapper>
		)
	}
}
const mapStateToProps =(state) => ({
	list:state.get('home').get('topicList')
})
export default connect(mapStateToProps,mapDispatchToProps)(Topic);

src/pages/home/components/List.js
import React, { Component }from 'react';
import { connect } from 'react-redux';
import { ListInfo,ListItem,LoadMore} from '../style';
import { actionCreators } from '../store';
import { Link }from'react-router-dom';
//由于home里项目比较小 我们把home各组件样式全部集中在pages/home/style.js
class List extends Component {
	render(){
		const { list,getMoreList,page }=this.props;
		return(
		<div>
		{
			list.map((item,index)=> {
				return (
				<Link key={item.get('id') to='/detail'>
					<ListItem }>
						<img className='pic' src={item.get('imgUrl')}
						alt=' '>
						<ListInfo>
							<h3 className='title'>{item.get('title')}</h3>
							<p className='desc'>{item.get('desc')}</p>
						</ListInfo>
					</ListItem>
					</Link>
				);
			}
			}
			<LoadMore onClick={()=>getMoreList(page)}>阅读更多</LoadMore>
		</div>
		)
	}
}
const mapStateToProps =(state) => ({
	list:state.getIn(['home','articleList'])
	page:state.getIn(['home','articlePage'])
});
const mapDispatchToProps =(dispatch) =>({
	getMoreList(page){
		dispatch(actionCreators.getMoreList(page))
	}
})
export default connect(mapStateToProps,mapDispatchToProps)(List);

home/components/Recommend.js
import React, { Component }from 'react';
import { RecommendWrapper,RecommendItem } from '../style';
import { connect } from 'react-redux';
class Recommend extends Component {
	render(){
		return(
			<RecommendWrapper>
				{this.props.list.map((item)=>{
					return(
					<RecommendItem imgUrl={item.get('imgUrl')}
									key={item.get('id')}
					>
					//注意防盗链问题 有就加http://..
					/RecommendItem>
					)
				})
			</RecommendWrapper>
		)
	}
}
const mapStateToProps = (state) => ({
	list:state.getIn(['home','recommendList'])//获取home下的recommendList
	
})
export default connect(mapStateToProps,null)(Recommend);

我们发现运行后/detail页面同时也把home页面显示出来了
但我们希望detail页面只显示detail
则需要在render属性前加exact
意思是路径完全匹配才显示该渲染的内容 而不是继承了根页面里的内容

在src下建立pages文件夹表示页面的集合 在里面创建home detail文件夹
home里创建index.js 同理detail
home里创建components放首页的组件 进行【组件拆分化】
home下创建store文件夹 在里面创建reducer.js

home/store/reducer.js

import { fromJS }from 'immutable';
const defaultState = fromJS ({
	topicList:[{
	id:1,
	title:'社会热点',
	imgUrl:"url"
	},{
	id:2,
	title:'手绘',
	imgUrl:"url2"
	}],
	articleList:[{
		id:1,
		title:'title',
		desc:'description',
		imgUrl:'url'
	}],
	recommendList:[{
		id:1,
		imgUrl:"url1"
		},
		{id:2,
		imgUrl:"url2"
		}],
		articlePage:1,
		showScroll:false
});

export default (state = defaultState ,action)=> {
	switch(action.type){
		case'change_home_data':
			//别忘了用fromJS转化成immutable对象
			return state.merge({
				topicList:fromJS(action.topicList),
				articleList:fromJS(action.articleList),
				recommendList:fromJS(action.recommendList)
			});
		case constants.ADD_ARTICLE_LIST:
			//别忘了用fromJS转化成immutable对象
			return state.merge{
			'articleList':state.get('articleList').concat(action.list))),
			'articlePage':action.nextPage
			//追加新数组
			};
		case constants.TOGGLE_SCROLL_TOP:
			return state.set('showScroll',action.show);
		default:
			return state;
		}
	}

src/store/reducer.js
import { combineReducers } from 'redux';//将小的reducer合并成大的reducer
//接下来引入小笔记本
import {reducer as headerReducer} from '../common/header/store/reducer';
import  homeReducer from '../pages/home/store/reducer';
//引用方式不同

const reducer = combineReducers({
	header:headerReducer,
	home:homeReducer//home由homeReducer管理
});
export default reducer;

为了引入方便,我们在小store下建立index.js

import reducer from './reducer';
export { reducer };

在这种方式下我们在大reducer下就需要这样引入
import {reducer as homeReducer} from '../pages/home/store';

(我们要做异步请求可以在主页面Home中用componentDidMount来进行ajax请求
把之前reducer里面的数据可以清空了)
可是以上操作用UI组件发送业务逻辑是不合适的,我们要进行优化,将逻辑放到mapDispatchToProps里。

鉴于函数里创建action很麻烦,我们在store下创建actionCreators.js

import axios from 'axios';
import { fromJS,List } from 'immutable';
//List 只是把数组最外层的一个变成immutable对象
//fromJS 里里外外都能变成immutable对象
const changeHomeData = (result) => ({
	type:constants.CHANGE_HOME_DATA,
	topicList:result.topicList,
	articleList:result.articleList,
	recommendList:result.recommendList
})
export const getHomeInfo = () => {
版本一:没有上面的changeHomeData
	return (dispatch) =>{
		axios.get('/api/home.json').then((res) => {
			const result = res.data.data;
			const action ={
				type:'change_home_data',
				topicList:result.topicList,
				articleList:result.articleList,
				recommendList:result.recommendList
	}
	dispatch(action);
})
}
版本二:有
	return (dispatch) =>{
		axios.get('/api/home.json').then((res)=> {
			const result=res.data.data;
			dispatch(changeHomeData(result));
			})
		}
}
const addHomeList= (list,nextPage) => ({
	type:constants.ADD_ARTICLE_LIST,
	list转化成:fromJS(list),
	nextPage
})
export const getMoreList =(page) =>{
	return (dispatch) =>{
		axios.get('/api/homeList.json?page='+page).then((res)=> {
			const result=res.data.data;
			dispatch(addHomeList(result,page+1));
			})
	}
export const toggleTopShow =(show) =>{
	type:constants.TOGGLE_SCROLL_TOP,
	show
	}


index.js
componentDidMount(){//页面挂载完毕时开始执行
		this.props.changeHomeData();
}
const mapDispatchToProps = (dispatch) => ({
	changeHomeData(){
			const action = actionCreators.getHomeInfo();
			//别忘了引入actionCreators
			dispatch(action);//所有小reducer都能接收到
		})
	}
	}
});

还可以进一步优化 将type变为常量 创建一个constants

当我们在首页往下翻时 需要做一个加载更多的功能\返回顶部的功能;
返回顶部的按钮在最上面时隐藏起来

当我们挂载完毕之后绑定了bindEvents,当组件移除时也需要取消windows上的绑定


性能优化:
数据一变化就要重新渲染页面
优化:可以在页面中加入shouldComponentUpdate(),然后让跟这个页面相关的数据变化时再渲染,但是每一个都写这个函数太麻烦了,react中内置了这样一个类型

home/index.js

import React,{ PureComponent } from 'react';
//把Component都改为Pure...
//区别:Pure自带shouldComponentUpdate(),不需要自己手写
//注意 用Pure但是不用immutable会遇到坑。。。

用< a>标签作跳转不符合单页面应用,浪费性能,
我们用Link替代,将href改为to,减少再生成一次html文件的浪费,加载快速

同理 header简书logo
css里将a改成div,
在index.js中用link包裹< logo />

注意App.js中
< header />要放在< Router>里面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七灵微

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值