一、路由
1)react-router 路由安装:
yarn add react-router-dom
2) App.js引入
import {BrowserRouter, Route} from 'react-router-dom';
exact : 路径完全跟根路径 完全相等的时候才显示相应内容
二、组件
1) 创建 pages: 项目有多少页面 ,本项目中home 和detail
2) App.js 引入 两个组件
import Home from './pages/home';
import Detail from './pages/detail';
<BrowserRouter>
<Route path = '/' exact component = {Home}></Route>
<Route path = '/detail' exact component = {Detail}></Route>
</BrowserRouter>
3)首页可以分为Topic,Recommend,List,Writer 四个组件
4)home 下的index.js 引入
**home 下的index.js**
import React, { Component } from "react";
import Topic from './commponent/Topic';
import List from './commponent/List';
import Recommend from './commponent/Recommend';
import Writer from './commponent/Writer';
import {
HomeWrapper,
HomeLeft,
HomeRight
} from './style';
class Home extends Component {
render() {
return (
<HomeWrapper>
<HomeLeft>
<img className='banner-img' src="" alt=""/>
<Topic/>
<List/>
</HomeLeft>
<HomeRight>
<Recommend/>
<Writer/>
</HomeRight>
</HomeWrapper>
)
}
}
export default Home;
5) home 下的style.js 文件
** style.js**
import styled from 'styled-components';
export const HomeWrapper = styled('div')`
width: 960px;
margin: 0 auto;
height: 300px;
overflow: hidden;
`;
export const HomeLeft = styled('div')`
margin_left: 15px;
padding-top: 30px;
width: 625px;
float: left;
.banner-img {
width: 625px;
height: 270;
}
`;
export const HomeRight = styled('div')`
width: 280px;
float: right;
`;
三、首页专题区域布局以及reducer 设计
####1) Topic 组件如何使用这些数据,Topic.js 组件代码
使用react-redux 的connect 方法
让Topic 组件和store 做连接
**pages/commponent/Topic.js 组件代码**
import React, { Component } from "react";
import { TopicWrapper, TopicItem } from '../style';
import { connect } from 'react-redux';
class Topic extends Component {
render() {
const { list } = this.props;
return (
<TopicWrapper>
{
list.map((item) => (
<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,null)(Topic);
// connect 包装好的容器组件
2) style.js 数据
/* 首页-专题*/
export const TopicWrapper = styled('div')`
overflow: hidden;
padding: 20px 0 10px 0;
margin-left: -10px;
border-bottom: 1px solid #dcdcdc;
`;
export const TopicItem = styled('div')`
float: left;
background: #f7f7f7;
height: 32px;
line-height: 32px;
margin-left: 18px;
padding-right: 10px;
font-size: 14px;
color: #000;
border: 1px solid #dcdcdc;
border-radius: 4px;
margin-bottom:18px;
.topic-pic {
display: block;
float: left;
width: 32px;
height: 32px;
margin-right: 10px;
}
`;
3)home 下创建store文件,index.js reducer.js
列表内容存在redux 中的store 中
store 的数据是由reducer 决定
reducer 可以拆很多部门
home 下的数据,一定要存在home 中对应的reducer
<!--reducer.js 内容-->
import { fromJS } from 'immutable';
// facebook immutable 库
// immutable 对象 yarn add immutable
// immutable 的数据,也会变成一个immutable 的数组
const defaultState = fromJS ({
topicList: [{
id: 1,
title: '社会热点',
imagUrl: ''
},{
id: 2,
title: '手绘',
imagUrl: ''
}]
});
export default (state = defaultState, action) => {
switch (action.type) {
default:
return state;
}
}
**index.js 内容**
import reducer from './reducer';
export { reducer } ;
4)大的reducer.js ,把home 下的reducer 引入
import { combineReducers } from 'redux-immutable';
import {reducer as headerReducer} from '../common/header/store';
import {reducer as HomeReducer} from '../pages/home/store';
const reducer = combineReducers({
header: headerReducer,
home: HomeReducer
})
export default reducer;
四、首页文章列表制作 跟专题其实是一个套路
1) List 组件代码
import React, { Component } from "react";
import { connect } from 'react-redux';
import {
ListItem,
ListInfo
} from '../style';
class List extends Component {
render() {
const { list } = this.props;
return (
<div>
{
list.map((item) => {
return (
<ListItem key={item.get('id')}>
<img class='pic' alt='' src= {item.get('imgUrl')} />
<ListInfo>
<h3 className='title'>{item.get('title')}</h3>
<p className='desc'>{item.get('desc') }</p>
</ListInfo>
</ListItem>
)
})
}
</div>
)
}
}
const mapStateToProps = (state) => ({
list: state.getIn(['home','articleList'])
})
export default connect(mapStateToProps,null)(List);
2) style.js 代码
/*首页-列表*/
export const ListItem = styled('div')`
overflow: hidden;
padding: 20px 0;
border-bottom: 1px solid #dcdcdc;
.pic {
display: block;
width: 125px;
height: 100px;
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;
}
`;
3)home 下面的组件中的数据取自 home 下面store 的reducer.js
import { fromJS } from 'immutable';
// facebook immutable 库
// immutable 对象 yarn add immutable
// immutable 的数据,也会变成一个immutable 的数组
const defaultState = fromJS ({
topicList: [{
id: 1,
title: '社会热点',
imgUrl: '//upload.jianshu.io/users/upload_avatars/7416466/fc1a1a0d-e3c7-4bca-9720-028c5c9914f3.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96/format/webp'
},{
id: 2,
title: '手绘',
imgUrl: '//upload.jianshu.io/users/upload_avatars/7416466/fc1a1a0d-e3c7-4bca-9720-028c5c9914f3.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96/format/webp'
}],
articleList: [
{
id: 1,
title: '爱不重,不生娑婆;爱不真,不生净土',
desc: '01 又是一年情人节,世间的人们,似乎很热闹。 热闹就对了,毕竟是有情众生。 不过,最近好像流行一个新词——情人劫。 因为,有情。所以,有劫。 ',
imgUrl: '//upload-images.jianshu.io/upload_images/12442510-f2b85983e1388d18?imageMogr2/auto-orient/strip|imageView2/1/w/360/h/240'
},
{
id: 5,
title: '爱不重,不生娑婆;爱不真,不生净土',
desc: '01 又是一年情人节,世间的人们,似乎很热闹。 热闹就对了,毕竟是有情众生。 不过,最近好像流行一个新词——情人劫。 因为,有情。所以,有劫。 ',
imgUrl: '//upload-images.jianshu.io/upload_images/12442510-f2b85983e1388d18?imageMogr2/auto-orient/strip|imageView2/1/w/360/h/240'
}
]
});
export default (state = defaultState, action) => {
switch (action.type) {
default:
return state;
}
}
五、首页推荐部分代码
1)组件 Recommend.js
import React, { Component } from "react";
import { connect } from 'react-redux';
import {
RecommendWrapper,
RecommendItem
} from '../style'
class Recommend extends Component {
render() {
const { list } = this.props;
return (
<RecommendWrapper>
{
list.map((item) => {
return (
<RecommendItem key={item.get('id')} imgUrl={item.get('imgUrl')}/>
)
})
}
</RecommendWrapper>
)
}
}
const mapStateToProps = (state) => ({
list: state.getIn(['home','recommendList'])
})
export default connect(mapStateToProps,null)(Recommend);
e>
2) style.js 代码
/* 首页-推荐*/
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;
`;
3)reducer.js ,添加 url 推荐列表数据
recommendList: [{
id: 1,
imgUrl: 'https://cdn2.jianshu.io/assets/web/banner-s-club-aa8bdf19f8cf729a759da42e4a96f366.png'
}, {
id: 2,
imgUrl: 'https://cdn2.jianshu.io/assets/web/banner-s-7-1a0222c91694a1f38e610be4bf9669be.png'
},{
id: 3,
imgUrl: 'https://cdn2.jianshu.io/assets/web/banner-s-5-4ba25cf5041931a0ed2062828b4064cb.png'
},{
id: 4,
imgUrl:'https://cdn2.jianshu.io/assets/web/banner-s-6-c4d6335bfd688f2ca1115b42b04c28a7.png'
}]
六、writer
七、异步数据
1)reduer.js 数据清空,创建home.json 文件
tips:JSON.stringfly() 对象转字符串
2) 引入 connect ,axios 模块,创建连接并调数据
import React, { Component } from "react";
import Topic from './commponent/Topic';
import List from './commponent/List';
import Recommend from './commponent/Recommend';
import Writer from './commponent/Writer';
import axios from 'axios';
import { connect } from 'react-redux';
import {
HomeWrapper,
HomeLeft,
HomeRight
} from './style';
class Home extends Component {
render() {
return (
<HomeWrapper>
<HomeLeft>
<img className='banner-img' alt='' src="//upload.jianshu.io/admin_banners/web_images/4674/674a8ac2283f0ff81f3fb4e37fce606cc474f129.png?imageMogr2/auto-orient/strip|imageView2/1/w/1250/h/540" alt=""/>
<Topic/>
<List/>
</HomeLeft>
<HomeRight>
<Recommend/>
<Writer/>
</HomeRight>
</HomeWrapper>
)
}
componentDidMount() {
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
}
this.props.changeHomeData(action);
}).catch(()=>{
})
}
}
const mapDispatch = (dispatch) => ({
changeHomeData(action) {
// 发送给store ,store 会转发给reducer ,
// 此reducer 是大的reducer,小的reducer 可以接收到
dispatch(action);
}
})
export default connect(null, mapDispatch)(Home);
3)reducer 接受type
import { fromJS } from 'immutable';
// facebook immutable 库
// immutable 对象 yarn add immutable
// immutable 的数据,也会变成一个immutable 的数组
const defaultState = fromJS ({
topicList: [],
articleList: [],
recommendList: []
});
export default (state = defaultState, action) => {
switch (action.type) {
case 'change_home_data' :
return state.merge({
topicList: fromJS(action.topicList),
articleList: fromJS(action.articleList),
recommendList: fromJS(action.recommendList),
})
default:
return state;
}
}
八、异步代码优化
1)Home 组件式一个UI 组件,connect 方法包装生成了容器组件
对于UI 组件来说,不应该有太多的业务逻辑
componentDidMount 中,ui 组件去发ajax 异步请求,获取内容,
然后在做逻辑处理,显示放在UI 组件中处理是不合适的。
优化,
componentDidMount() {
this.props.changeHomeData();
}
const mapDispatch = (dispatch) => ({
changeHomeData() {
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);
})
// 发送给store ,store 会转发给reducer ,
// 此reducer 是大的reducer,小的reducer 可以接收到
}
})
优化后,把ui 组件的业务逻辑剔除掉了
取而代之,mapdisPathch 是容器组件
但是ajax 异步请求一般不放在容器组件中
使用redux-thunk 中间件,可以放在 action 里面管理
action 如何创建? actionCreator.sj
home——>store ——> index.js 是actionCreators一个出口,
也是常量文件constants.js 的出口
**home——>store ——> index.js 的代码**
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer, actionCreators, constants };
**home——>store ——> actionCreator.js 的代码**<pre>
import axios from 'axios';
import * as constants from './constants';
// changeHomeData 等于一个函数,返回的是个对象
const changeHomeData = (result) =>({
type: constants.CHANGE_HOME_DATA,
topicList: result.topicList,
articleList: result.articleList,
recommendList: result.recommendList
})
// redux-thunk 使得我们可以返回一个函数,函数会顺序的执行
export const getHomeInfo = () => {
return (dispatch) => {
axios.get('/api/home.json').then((res) => {
const result = res.data.data;
dispatch(changeHomeData(result));
})
}
}
**home——>store** ——> constants.js 的代码
// 文件的出口还是 store 下的index.js
export const CHANGE_HOME_DATA = 'home/CHANGE_HOME_DATA';
九、实现加载更多
1) List 组件 代码
import React, { Component } from "react";
import { connect } from 'react-redux';
import { actionCreators } from '../store';
import {
ListItem,
ListInfo,
LoadMore, // 添加加载更多模块
} from '../style';
class List extends Component {
render() {
const { list, getMoreList, page } = this.props;
return (
<div>
{
list.map((item,index) => {
return (
<ListItem key={index}>
<img class='pic' alt='' src= {item.get('imgUrl')} />
<ListInfo>
<h3 className='title'>{item.get('title')}</h3>
<p className='desc'>{item.get('desc') }</p>
</ListInfo>
</ListItem>
)
})
}
<LoadMore onClick={ ()=> getMoreList(page) }>更多文字</LoadMore>
</div>
)
}
}
const mapStateToProps = (state) => ({
list: state.getIn(['home','articleList']),
page: state.getIn(['home','articlePage'])
})
const mapDispatch = (dispatch) => ({
getMoreList(page) {
dispatch(actionCreators.getMoreList(page));
}
})
export default connect(mapStateToProps,mapDispatch)(List);
2) style.js 代码
export const LoadMore = styled('div')`
width: 100%;
height: 40px;
line-height: 40px;
margin: 30px 0;
background: #a5a5a5;
text-align: center;
border-radius:20px;
color: #fff;
cursor: pointer;
3) constants.js 增加代码
export const ADD_ARTICLE_LIST = 'home/ADD_ARTICLE_LIST';
4) reducer.js 代码变动
import { fromJS } from 'immutable';
import * as constants from './constants';
const defaultState = fromJS ({
topicList: [],
articleList: [],
recommendList: [],
articlePage: 1 // 增加页码
});
export default (state = defaultState, action) => {
switch (action.type) {
case constants.CHANGE_HOME_DATA :
return state.merge({
topicList: fromJS(action.topicList),
articleList: fromJS(action.articleList),
recommendList: fromJS(action.recommendList),
});
case constants.ADD_ARTICLE_LIST:
return state.merge({
'articleList':state.get('articleList').concat(action.list),
'articlePage': action.nextPage
})
default:
return state;
}
}