首页的制作
引入路由组件依赖: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,这样会损害性能
如何优化呢?
- 方法一:用shouldComponentUpdate这一生命周期函数进行判断
每个组件都写这个判断函数,有些复杂 - 方法二:用PureComponent代替Component
import React, { PureComponent } from "react";
所有用Component 的地方改成用 PureComponent
PureComponent底层内置了这一判断,与自己无关的store更新就不会重新渲染
注意:方法二最好与immutable一起使用,不然会有些坑,不用immutable的组件就只能采用法1