首页开发(2)

文章列表的制作(List.js)

文章列表也就是home-components-List.js中的 List组件
List组件

步骤类似Topc组件

  1. 在home-style.js中,定义List组件需要的样式
  2. 在List.js中,使用组件进行布局使用组件
  3. 在home-store-reducer.js中,存储ListItem中需要的数据
  4. 在List.js中,读取reducer中存储在state里的数据,循环渲染为ListItem组件

代码

home-style.js中,定义List组件需要的样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export const ListItem = styled.div`
display: flow-root;
padding: 20px 0;
border-bottom: 1px solid #dcdcdc;
.pic {
float: right;
width: 125px;
height: 100px;
border-radius: 10px;
}
`;

export const ListInfo = styled.div`
width: 500px;
.title {
line-height: 27px;
font-size: 18px;
font-weight: bold;
color: #333;
}
.desc {
line-height: 24px;
font-size: 13px;
color: #999;
}
`;

home-store-reducer.js中,存储ListItem中需要的数据,在defaultState中增加articleList数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const defaultState = fromJS({
topicList:[{
id:1,
title:"社会热点",
imgUrl: "https://upload.jianshu.io/users/upload_avatars/3136195/484e32c3504a.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96/format/webp"
},{
id:2,
title:"手绘",
imgUrl: "https://upload.jianshu.io/users/upload_avatars/3136195/484e32c3504a.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96/format/webp"
}],
articleList:[{
id:1,
title:"疫情就要结束了!",
dexc: "因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...",
imgUrl: "https://upload-images.jianshu.io/upload_images/14964218-bbc6a0ef3f52889e.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/360/h/240"
},{
id:2,
title:"在家长厨艺",
dexc: "今年,大家都过了一个 长长长长长长的假期 每天朋友圈里 除了有关疫情的新闻 就是晒自己做的“黑暗料理” 在这个没有了外卖的日子里 蛋糕、奶茶、火...",
imgUrl: "https://upload-images.jianshu.io/upload_images/19325519-7ffb94bacd4ff304?imageMogr2/auto-orient/strip|imageView2/1/w/300/h/240"
},{
id:3,
title:"大家要坚持戴口罩!",
dexc: "疫情对普通人的影响,很大程度上表现为一场魔幻式的恐慌。从朋友圈疯传的段子就可以看出,最搞笑的是某宝平台上的兽用双黄莲也被抢购一空。虽然我国制度优...",
imgUrl: "https://upload-images.jianshu.io/upload_images/19972340-9a06362a842656a2.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/300/h/240"
}]
});

List.js中,读取reducer中存储在state里的数据,循环渲染为ListItem组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component, Fragment } from 'react';
import {
ListItem,
ListInfo,
} from "../style";
import { connect } from "react-redux";

class List extends Component {
render() {
return (


<Fragment>
{
this.props.list.map((item) => {
return (
<ListItem key={item.get("id")}>
<img
className="pic"
src={item.get("imgUrl")}
alt={item.get("title")}
/>
<ListInfo>
<h3 className="title">{item.get("title")}}</h3>
<p className="desc">{item.get("desc")}}</p>
</ListInfo>
</ListItem>
)
})
}
</Fragment>
)
}
}

const mapState = (state) => ({
list: state.getIn(["home", "articleList"])
})

export default connect(mapState, null)(List);

效果


样式属性值可从使用组件(props)传递

  • style.js样式的属性值可以是从使用组件中传递过来的属性值,这样使用同一个样式组件时可以通过传递不同的属性值达到不同的效果
    1. style.js中,使用 模板字符串 中的${(props)=>props.属性名}来设置样式属性值
    2. 使用组件时,通过<组件名 属性名={属性值}></组件名>将 属性值 传到style.js中作为 样式属性值
  • 例子如下 RecommendItem组件background样式属性值就是由使用组件时传入的imgUrl属性值决定的。
  • 可参考官网的实例

推荐部分的编写(Recommend.js)

推荐部分也就是home-components-Recommend.js中的 Recommend组件Recommend.js

步骤:

  1. 在home-style.js中,定义Recommend组件需要的样式
  2. 在 Recommend.js中,使用组件进行布局
  3. 在home-store-reducer.js中,存储Recommend中需要的数据
  4. 在 Recommend.js中,读取reducer中存储在state里的数据,循环渲染为RecommendItem组件

代码

style.js:

1
2
3
4
5
6
7
8
9
10
11
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;
`;

Recommend.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component } from 'react';
import { connect } from "react-redux";
import {
RecommendWrapper,
RecommendItem
} from "../style";

class Recommend extends Component {
render() {
return (
<RecommendWrapper>
{
this.props.list.map((item) => {
return (
<RecommendItem
imgUrl={item.get("imgUrl")}
key={item.get("id")}
></RecommendItem>
)
})
}
</RecommendWrapper>
)
}
}

const mapState = (state) => ({
list: state.getIn(["home", "recommendList"])
})

export default connect(mapState, null)(Recommend);

reducer.js中增加:

1
2
3
4
5
6
7
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"
}],

效果


Writer.js的编写

思路其实还是差不多的。

style.js中增加:

1
2
3
4
5
6
7
export const WriterWrapper = styled.div`
width: 278px;
border: 1px solid #dcdcdc;
border-radius: 3px;
line-height: 300px;
text-align: center;
`;

Writer.js:

1
2
3
4
5
6
7
8
9
10
11
12
import React, { Component } from 'react';
import { WriterWrapper } from "../style";

class Writer extends Component {
render() {
return (
<WriterWrapper>Writer~</WriterWrapper>
)
}
}

export default Writer;

效果


异步获取首页数据

  • 之前我们的数据都是放在前端reducer之中的,可实际上是应该通过接口从后端获取数据的。
  • 所以我们需要:
    1. public-api文件夹下新建home.json文件,将模拟的接口数据放在json文件中
      • 注意:JSON中除了boolean和数字、null,其他都要加双引号
    2. home-index.js(首页)中,页面加载完毕后,发送AJAX请求获取接口数据
    3. 通过connect的参数2的回调函数将获取到的数据通过dispatch派发到store中,store中的数据改变,页面自然重新渲染
      • 思路:修改store中数据=>需派发action=>在react-redux中使用connect方法的参数2的dispatch
    4. home-reducer.js中处理store传送过来的action和state,将获取到的数据放入store中,state数据发生变化,页面自然重新渲染
      • 注意
        1. action被派发后,大的小的reducer都能接收到
        2. 使用set()修改immutable对象的方法时,必须保证替换的数据也是immutable类型的,如若不是,则可使用fromJS()将普通JS对象改为immutable对象
        3. 一开始的初始数据还是要给空数组,不能全部删除。

json文件模拟接口数据

注意:JSON中除了boolean和数字、null,其他都要加双引号

public-api-home.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
"sucess":true,
"data":{
"topicList": [{
"id": 1,
"title": "社会热点",
"imgUrl": "https://upload.jianshu.io/users/upload_avatars/3136195/484e32c3504a.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96/format/webp"
}, {
"id": 2,
"title": "手绘",
"imgUrl": "https://upload.jianshu.io/users/upload_avatars/3136195/484e32c3504a.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96/format/webp"
}],
"articleList": [{
"id": 1,
"title": "疫情就要结束了!",
"desc": "因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...",
"imgUrl": "https://i0.wp.com/ww1.sinaimg.cn/large/005H7IVsgy1gcuniztyfhj30dw09tai6.jpg"
}, {
"id": 2,
"title": "在家长厨艺",
"desc": "今年,大家都过了一个 长长长长长长的假期 每天朋友圈里 除了有关疫情的新闻 就是晒自己做的“黑暗料理” 在这个没有了外卖的日子里 蛋糕、奶茶、火...",
"imgUrl": "https://i0.wp.com/ww1.sinaimg.cn/large/005H7IVsgy1gcunif28rwj30rs0iitzs.jpg"
}, {
"id": 3,
"title": "大家要坚持戴口罩!",
"desc": "疫情对普通人的影响,很大程度上表现为一场魔幻式的恐慌。从朋友圈疯传的段子就可以看出,最搞笑的是某宝平台上的兽用双黄莲也被抢购一空。虽然我国制度优...",
"imgUrl": "https://i0.wp.com/ww1.sinaimg.cn/large/005H7IVsgy1gcunhi0u4qj30ib0au79t.jpg"
}],
"recommendList": [{
"id": 1,
"imgUrl": "https://i0.wp.com/ww1.sinaimg.cn/large/005H7IVsgy1gcunpxdxm5j30fk02sgli.jpg"
}, {
"id": 2,
"imgUrl": "https://i0.wp.com/ww1.sinaimg.cn/large/005H7IVsgy1gcunq7zbr1j30fk02s3yi.jpg"
}]
}
}

首页发送AJAX请求,获取数据传到store

  1. home-index.js(首页)中,页面加载完毕后,发送AJAX请求获取接口数据。
  2. 通过connect的参数2的回调函数将获取到的数据通过dispatch派发到store中,store中的数据改变,页面自然重新渲染
    • 思路:修改store中数据=>需派发action=>在react-redux中使用connect方法的参数2的dispatch

home-index.js:

1
2
import axios from "axios";
import { connect } from "react-redux";

中间未改动部分省略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  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);
})
}
}

const mapDisoatch = (dispatch) => ({
changeHomeData(action) {
dispatch(action);
}
});

export default connect(null, mapDisoatch)(Home);

reducer处理数据

home-reducer.js中处理store传送过来的action和state,将获取到的数据放入store中,state数据发生变化,页面自然重新渲染

注意

  1. action被派发后,大的小的reducer都能接收到
  2. 使用set()修改immutable对象的方法时,必须保证替换的数据也是immutable类型的,如若不是,则可使用fromJS()将普通JS对象改为immutable对象
  3. 一开始的初始数据还是要给空数组,不能全部删除。

home-reducer.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { fromJS } from "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-index.js中的Home组件是UI组件,所以逻辑操作不应该放在Home组件中,我们可以将逻辑操作放到mapDisoatch方法中
  2. AJAX请求可以使用redux-thunk中间件放在action中
  3. home-store中新建actionCreators.js用于统一创建action,index.js作为唯一出口。
  4. home-store中新建constants.js用于将action中的type值从字符串变为常量。
  5. 修改home-reducer.js中的type值

统一管理action的type值

home-store中新建constants.js用于将action中的type值从字符串变为常量:

1
export const CHANGE_HOME_DATA = "change_home_data";

index.js作为唯一出口:

1
2
3
4
import reducer from "./reducer";
import * as constants from "./constants";

export { reducer, constants };

统一创建action(redux-thunk)

home-store中新建actionCreators.js用于统一创建action。使用redux-thunk中间件,可使AJAX请求放在action中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import axios from "axios";
import * as constants from "./constants";

const changeHomeData = (result) => ({
type: constants.CHANGE_HOME_DATA,
topicList: result.topicList,
articleList: result.articleList,
recommendList: result.recommendList
})

export const getHomeInfo = () => {
return (dispatch) => {
axios.get("/api/home.json").then((res) => {
const result = res.data.data;
dispatch(changeHomeData(result));
})
}
}

index.js作为唯一出口:

1
2
3
4
5
import reducer from "./reducer";
import * as actionCreators from "./actionCreators";
import * as constants from "./constants";

export { reducer, actionCreators, constants };

逻辑放到容器组件的函数中

home-index.js中,原本我在componentDidMount()中发送AJAX请求,现在将AJAX请求放到action中,在mapDispatch()中的changeHomeData()内创建并派发此action,在componentDidMount()中只需调用changeHomeData()即可发送AJAX请求

1
import { actionCreators } from "./store";
1
2
3
componentDidMount() {
this.props.changeHomeData();
}
1
2
3
4
5
6
const mapDisoatch = (dispatch) => ({
changeHomeData() {
const action = actionCreators.getHomeInfo();
dispatch(action);
}
});

修改reducer的type值

修改home-reducer.js中的type值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { fromJS } from "immutable";
import * as constants from "./constants";

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

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)
})
default:
return state;
}
}
,