从零开始React项目04

首页的制作

引入路由组件依赖:react-router-dom

由于版本问题,暂不考虑使用最新版@6的版本

npm i react-router-dom@5

src/App.js

import { BrowserRouter, Route } from "react-router-dom";
class App extends Component {
  render() {
    return (
      <Provider store={store}>
			...
        <Header />
        <BrowserRouter>
          <Route path="/" exact render={() => <div>home</div> }></Route>
          <Route path="/detail" exact render={() =>  <div>detail</div> }></Route>
        </BrowserRouter>
      </Provider>
    );
  }
}

在地址栏输入对应的地址,就会跳转显示对应内容,最终目的是显示相应的组件页面

新建home和detail组件

在这里插入图片描述
每个组件暂时编写简单内容

import  React ,{Component} from "react";

class Home extends Component{
    render() {
        return (
            <div>Home</div>
        )
    }
}
export default Home;

src/App.js引入

import Home from "./pages/home";
import Details from "./pages/detail";

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <Globalstyle></Globalstyle>
        <GlobalFont/>
        <Header />
        <BrowserRouter>
          <Route path="/" exact component={Home}></Route>
          <Route path="/detail" exact component={Details}></Route>
        </BrowserRouter>
      </Provider>
    );
  }
}
export default App;

Home组件的雏形

src/pages/home/index.js

import React, { Component } from "react";
import {
    HomeWrapper,
    HomeRight,
    HomeLeft
} from "./style";

class Home extends Component{
    render() {
        return (
            <HomeWrapper>
                <HomeLeft>
                    <img className="banner-img" src="https://upload.jianshu.io/admin_banners/web_images/5055/348f9e194f4062a17f587e2963b7feb0b0a5a982.png?imageMogr2/auto-orient/strip|imageView2/1/w/1250/h/540"></img>
                </HomeLeft>
                <HomeRight>right</HomeRight>
            </HomeWrapper>
        )
    }
}
export default Home;

src/pages/home/style.js

import styled from "styled-components";
const HomeWrapper = styled.div`
  overflow:hidden;
  width:960px;
  margin:0 auto;
`
export const HomeLeft = styled.div`
    float:left;
    margin-left:15px;
    padding-top:30px;
    width:625px;
    .banner-img{
        width:625px;
        height:270px;
    }
`
export const HomeRight = styled.div`
    float:right;
    width:270px;
`
export { HomeWrapper };

Home组件内增添小的子组件

在这里插入图片描述
每个都先简单写个单词,暂时无内容。写完再在src/pages/home/index.js中引入

import List from "./componets/List";
import Recommend from "./componets/Recommmend";
import Topic from "./componets/Topic";
import Writer from "./componets/Writer";
<HomeLeft>
   <img className="banner-img" src="https://upload.jianshu.io/admin_banners/web_images/5055/348f9e194f4062a17f587e2963b7feb0b0a5a982.png?imageMogr2/auto-orient/strip|imageView2/1/w/1250/h/540"></img>
   <Topic />
   <List />
</HomeLeft>
<HomeRight>
   <Recommend />
   <Writer/>
</HomeRight>

效果图
我的盲点:

  • 引入同级文件,要用 “./” , 上级文件要用 “../”, 前者一个点,后者两个点
  • CSS的BFC,暂时还不懂,按照老师的说法,内层浮动,外层就要overflow:hidden,这样就能触发BFC,使得父元素的高度能够适应内部子元素的高度

topic组件

import React, { Component } from "react";
import { TopicWrapper, TopicItem} from "../style";//上层文件要用俩点

class Topic extends Component {
    render() {
        return (
            <TopicWrapper>
                <TopicItem>社会热点</TopicItem>//接下来这里的数据,要存在本级目录下的store中
            </TopicWrapper>
        )
    }
}
export default Topic;
export const TopicWrapper = styled.div`
    overflow:hidden;
    padding:20px 0 10px 0;
    margin-left:18px;
`
export const TopicItem = styled.div`
    float:left;
    height:32px;
    line-height:32px;
    margin-right:18px;
    margin-bottom:10px;
    padding-right:10px;
    padding-left:10px;
    background:#f7f7f7;
    font-size:14px;
    color:#000;
    border:1px solid #dcdcdc;
    border-radius:4px;
`

新建src/pages/home/store/( reducer.js+index.js )

这一文件夹用于管理和存放所有home组件下的小组件数据和行为
首先把topic的相关数据放进来
src/pages/home/store/ reducer.js

import { fromJS } from "immutable";// 导入immutable模块中fromJS方法
//使用fromJS将其变为immutable对象数据(只能使用set、get)
const defaultState = fromJS({
    topicList: [{
        id: 1,
        title:"社会热点"
    }, {
        id: 2,
        title:"手绘"
    }]
})
export default (state = defaultState, action) => {
    //immutable对象的set方法,会结合之前immutable对象
    //和设置的值,会返回一个全新的对象
    switch (action.type) {
        default:
            return state;
    }
};

src/pages/home/componets/topic.js

import React, { Component } from "react";
import { TopicWrapper, TopicItem } from "../style";//上层文件要用俩点
import { connect } from "react-redux";//只有引入了这一方法才能使topic可以接触到全局的store

class Topic extends Component {
    render() {
        return ( 
            <TopicWrapper>
                {
                    this.props.list.map((item) => {
                        return (
                            <TopicItem key={item.get("id")}>{item.get("title")}</TopicItem>
                       )
                    })
                }
                
            </TopicWrapper>
        )
    }
}
const mapStateToProps = (state) => ({//这一函数返回一个对象
    list:state.get("home").get("topicList")
})
const mapDispatchToProps = (dispatch) => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(Topic);

这里的这个this.props.list.map( ( item )=>{ } )函数值得我好好学习一下。

  • 首先,this.props.list.map( ( item )=>{ return ( 对象) } )
    这里相当于每历经到一个item,就返回一个对象
  • 其次,这里的每个item,由于被fromJS变成immutable对象,他们的内容也得用item.get(" key名")

src/pages/home/componets/list.js

import React, { Component } from "react";
import { ListItem, ListInfo } from "../style"
import { connect } from "react-redux";

class List extends Component {
    render() {
        const { list } = this.props;
        return (
            <div>
                {list.map((item) => {
                    return (
                        <ListItem key={ item.get("id")}>
                            <img className="pic" src={item.get("imgUrl") }></img>
                            <ListInfo>
                                <h3 className="title">{item.get("title") }</h3>
                                <br />
                                <p className="desc">{ item.get("desc")}</p>
                            </ListInfo>
                        </ListItem>
                    )
                })}
            </div>
        )
    }
}
const mapStateToProps = (state) => ({//这一函数返回一个对象
    list: state.get("home").get("articleList")
})
const mapDispatchToProps = (dispatch) => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(List);

src/pages/home/store/ reducer.js新增对应List的数据

    articleList: [{
        id: 1,
        title: "掌握一项技能就像做菜,需要多加练习",
        desc:"学习前端,就好像学习做菜一样。有好的厨具,还需要掌握厨具的使用方法,厨艺练的炉火纯青,最后才能做出好菜。前端的厨具有很多,最基本的三大件,到各种框架,各种辅助工具,知道了这些,还要多加联系,多实践,才能做出漂亮的页面",
        imgUrl:"https://pic32.photophoto.cn/20140901/0010023230328831_b.jpg"
    }, {
        id: 2,
        title:"我只相信,这个世界上,多掌握些东西,一定没错!",
        desc:"今年的铜三铁四已经非常明显了,周围同学都在说逃离互联网,也有不少同学投通信岗位,但我坚信,未来互联网一定有其用武之地,像我的家乡,距离互联网业务仍很遥远",
        imgUrl: "https://tse4-mm.cn.bing.net/th/id/OIP-C.LCTO_4L8xnhLwnEUThTD3AHaHa?pid=ImgDet&rs=1"
    }, {
        id: 3,
        title:"书籍是进步成长的阶梯,也不要忘记多读技术文档",
        desc:"从小到大,我一直习惯于从书本获取知识,但现在更多的是从互联网上,获取知识,这点我不敢说自己很娴熟,成长和学习是一生的事。也不要忘记最喜欢的阅读,阅读可以扩宽人生的宽度,看到他人的经历",
        imgUrl:"https://img-u-4.51miz.com/Templet/00/24/86/90/248690_39fc6d6481dd2676529ce333adc8f3e3.jpg-1.jpg"
    }, {
        id: 4,
        title:"有人陪伴既是幸福,也是责任",
        desc:"没有人能一直陪伴自己,倘若有,一定要好好珍惜,不能辜负他人的陪伴。不管是任何关系,朋友、恋人、家人都需要付出时间精力去维护,不能当作理所当然,珍惜他人的陪伴",
        imgUrl:"https://img95.699pic.com/element/40049/5296.png_860.png"
    }]

初具雏形

其他的以此类推,不再详述

首页数据异步获取

src/pages/home/store/ reducer.js的数据全部剪切到新建的public/api/home.json中去
模拟从后端中拿到json数据
盲点:json数据需要所有的数据被包括在“ ”双引号中,但凡是字符串,都需要被包裹住
这些数据应该在什么时候获取到呢?
应该在Home组件加载完成后,发送AJAX请求
src/pages/home/index.js

import axios from "axios";
。。。
class Home extends Component {
    render() {
        return (
            <HomeWrapper>
				。。。
            </HomeWrapper>
        )
    }
    componentDidMount() {
        axios.get("/api/home.json").then((res) => {
            const result = res.data.data;
            console.log(result);
        })
    }
}
export default Home;

在这里插入图片描述
接下来将请求得到的数据,派发给store,store再给reducer,reducer再用数据修改原来的默认空数据
如何将数据派发给store呢?
老办法,redux的 connect将home与store数据连接起来
src/pages/home/index.js

   componentDidMount() {
        axios.get("/api/home.json").then((res) => {
            const result = res.data.data;
            const action = {
                type: "change_home_data",
                articleList: result.articleList,
                recommendList: result.recommendList,
                topicList: result.topicList
            }
            this.props.changeHomeData(action);
        })
    }
const mapDispatchToProps = (dispatch) => ({
    changeHomeData(action) {
        dispatch(action);
    }
})
export default connect(null,mapDispatchToProps)(Home);

这里的dispatch(action)其实不管是总的store的reducer还是各分支store的reducer,都能收得到
src/pages/home/store/ reducer.js做出相应的反应

import { fromJS } from "immutable";// 导入immutable模块中fromJS方法
//使用fromJS将其变为immutable对象数据(只能使用set、get)
const defaultState = fromJS({
    topicList: [],
    articleList: [],
    recommendList: []
})
export default (state = defaultState, action) => {
    switch (action.type) {
        case "change_home_data":
            return state.set("topicList", fromJS(action.topicList)).set("articleList", fromJS(action.articleList)).set("recommendList", fromJS(action.recommendList))
            //上面这种写法比较长且low,可以用merge方法,如下:
            return state.merge({
                topicList: fromJS(action.topicList),
                articleList: fromJS(action.articleList),
                recommendList: fromJS(action.recommendList)
            })         
        default:
            return state;
    }
};

代码优化

1.UI组件(负责渲染)和容器组件(负责动作)相分离的原则

修改src/pages/home/index.js

componentDidMount() {
    this.props.changeHomeData();
}
const mapDispatchToProps = (dispatch) => ({
    changeHomeData() {
        axios.get("/api/home.json").then((res) => {
            const result = res.data.data;
            const action = {
                type: "change_home_data",
                articleList: result.articleList,
                recommendList: result.recommendList,
                topicList: result.topicList
            }
            dispatch(action);
        })   
    }
})

2.上面changeHomeData() 的action逻辑,应由actionCreator来写

3. actionCreator中的type是个常量,应由actionType(或命名为constant)来写

实现list底部点击加载更多内容功能

src/pages/home/components/list.js

class List extends Component {
    render() {
        const { list, getMoreList } = this.props;
        return (
            <div>
     			。。。。。。
                <LoadMore onClick={ getMoreList }>更多内容</LoadMore>
            </div>
        )
    }
}
const mapDispatchToProps = (dispatch) => ({
    getMoreList() {
        dispatch(actionCreator.getMoreList());
    }
})
export default connect(mapStateToProps, mapDispatchToProps)(List);

src/pages/home/store/actionCreator.js

import { fromJS } from "immutable";

const addArticleList = (list) => ({
    type: actionType.ADD_ARTICLE_LIST,
    list: fromJS(list)
})
export const getMoreList=() => {
    return (dispatch) => {
        axios.get('/api/homeList.json').then((res) => {
            const result = res.data.data;
            dispatch(addArticleList(result));
        })
    }
}

src/pages/home/store/reducer.js

const defaultState = fromJS({
    topicList: [],
    articleList: [],
    recommendList: []
})
export default (state = defaultState, action) => {
    switch (action.type) {
			......
        case actionType.ADD_ARTICLE_LIST:
            return state.set("articleList", state.get("articleList").concat(action.list))
        default:
            return state;
    }
}

由于数据写死了,多次点击之后,就会出现key值冲突的问题
改成用index值去替换原来的id值

   {list.map((item,index) => {
             return (
                 <ListItem key={index}>
                     <img className="pic" src={item.get("imgUrl") }></img>
                     <ListInfo>
                         <h3 className="title">{item.get("title") }</h3>
                         <br />
                         <p className="desc">{ item.get("desc")}</p>
                     </ListInfo>
                 </ListItem>
             )
         })}

实现返回顶部的组件

src/pages/home/index.js

import { BackTop } from "./style";

class Home extends Component {
    handleScrollTop() {
        window.scrollTo(0, 0);//使用Web API
    }
    render() {
        return (
           <BackTop onClick={this.handleScrollTop}>回到顶部</BackTop>
        )
    }
}
export default connect(null,mapDispatchToProps)(Home);

由于功能比较简单,所以直接在这里全写了。但接下来,我们想实现,在他本来就在顶部的时候,“回到顶部”这一组件不显示,而往下划到一定程度的时候,才会出现这一组件。
reducer中定义一个变量showScroll:false用来控制是否显示

const defaultState = fromJS({
    topicList: [],
    articleList: [],
    recommendList: [],
    showScroll: false
})

然后再在src/pages/home/index.js中定义mapStateToProps函数获取到这个变量的值

const mapStateToProps = (state) => ({
    showScroll: state.getIn(["home","showScroll"])
})

Home/index.js中依据这一变量的值,showScroll:false不显示,showScroll:true显示

   {this.props.showScroll ? <BackTop onClick={this.handleScrollTop}>回到顶部</BackTop>:null
   }

接下来就是编写action函数,调控这一变量的值。由于这一变量的值与window.scroll有关,因此需要绑定事件addEventListener
绑定事件要在 componentDidMount生命周期函数中

  componentDidMount() {
        this.props.changeHomeData();
        this.bindEvents();
    }
    bindEvents() {
        window.addEventListener("scroll",this.props.changeScrollTopShow)
    }

const mapDispatchToProps = (dispatch) => ({
    changeScrollTopShow(e) {  //新增,这样我们以上下滑动,就会不断打印出当前的scroll值
        console.log(e);
        console.log(document.documentElement.scrollTop);
    }
})

盲点 :document.documentElement.scrollTop这个web api的数值是这个元素的内容顶部(已经不可见)到它的视口可见内容(的顶部)的距离

   changeScrollTopShow() {
        if (document.documentElement.scrollTop > 500) {
            dispatch(actionCreator.toggleTopShow(true));
            //令showScroll变量为true
        } else {
            dispatch(actionCreator.toggleTopShow(false))
            //令showScroll变量为false
         };
    }

接下来去写这个action
src/pages/home/store/actionCreator.js中增加

export const toggleTopShow = (showflag) => ({
    type: actionType.TOGGLE_SCROLL_TOP,
    showflag
})

src/pages/home/store/reducer.js中增加

 case actionType.TOGGLE_SCROLL_TOP:
      return state.set("showScroll", action.showflag);

功能实现!
最后收尾工作,在一开始,我们在 componentDidMount生命周期函数中绑定事件,意味着组件挂载完成之后,我们往window上绑定了一个scroll事件,在组件卸载的时候,我们要把这个绑定事件消除才可以

   componentDidMount() {
        this.props.changeHomeData();
        this.bindEvents();挂载完成时绑定事件
    }
    componentWillUnmount() {卸载时,移除这个绑定事件
        window.removeEventListener("scroll", this.props.changeScrollTopShow)
    }
    bindEvents() {
        window.addEventListener("scroll",this.props.changeScrollTopShow)
    }

代码性能优化

用PureComponent代替Component

由于每个组件都用react-redux与统一的store做了connect,只要是store的内容发生了改变,所有的组件都会重新render,这样会损害性能
如何优化呢?

  1. 方法一:用shouldComponentUpdate这一生命周期函数进行判断
    每个组件都写这个判断函数,有些复杂
  2. 方法二:用PureComponent代替Component
import React, { PureComponent } from "react";

所有用Component 的地方改成用 PureComponent
PureComponent底层内置了这一判断,与自己无关的store更新就不会重新渲染

注意:方法二最好与immutable一起使用,不然会有些坑,不用immutable的组件就只能采用法1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值