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>里面