首页开发:react-router

250 阅读7分钟

一、路由

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;
    }
}