详情页面开发

详情页面布局

pages-detail下新建style.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
import styled from "styled-components";

export const DetailWrapper = styled.div`
overFlow: hidden;
width: 620px;
margin: 0 auto;
padding-bottom: 100px;
`;

export const Header = styled.div`
margin: 50px 0 20px 0;
line-height: 44px;
font-size: 34px;
color: #333;
font-weight: bold;
`;

export const Content = styled.div`
color: #2f2f2f;
img{
width: 100%;
}
p{
margin: 25px 0;
font-size: 16px;
line-height: 30px;
}
`;

在pages-detail-index.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
import React, { Component } from 'react';
import {
DetailWrapper,
Header,
Content,
} from "./style";

class Detail extends Component {
render() {
return (
<DetailWrapper>
<Header>疫情就要结束了!</Header>
<Content>
<img
src="https://i0.wp.com/ww1.sinaimg.cn/large/005H7IVsgy1gcuniztyfhj30dw09tai6.jpg"
alt="疫情就要结束了!" />
<p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>
<p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>
<p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>
<p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>
</Content>
</DetailWrapper>
)
}
}

export default Detail;

效果


使用redux管理数据

  • detail下创建store文件夹,并在该文件夹下创建
    • actionCreators.js:创建action以及发生AJAX请求
    • constants.js:将action的type转为变量
    • reducer.js:放置state默认数据,处理action
    • index.js:统一对外接口

创建 reducer.js

先创建reducer.js,因为创建store需要reducer。

detail-store-reducer.js:

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({
title: "疫情就要结束了!",
content: `<img
src="https://i0.wp.com/ww1.sinaimg.cn/large/005H7IVsgy1gcuniztyfhj30dw09tai6.jpg"
alt="疫情就要结束了!" />
<p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>
<p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>
<p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>
<p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>`
});

export default (state = defaultState, action) => {
switch (action.type) {

default:
return state;
}
}

创建 index.js

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

将detail的reducer合并进项目的reducer中

src-store-reducer.js是由项目中的小的reducer组合而来,我们要将detail的reducer合并进项目的reducer中,这样才能通过store获取到state中的数据

src-store-reducer.js:

1
2
3
4
5
6
7
8
9
10
11
12
import { combineReducers } from "redux-immutable";
import { reducer as headerReducer } from "../common/header/store";
import { reducer as homeReducer } from "../pages/home/store";
import { reducer as detailReducer } from "../pages/detail/store";

const reducer = combineReducers({
header: headerReducer,
home: homeReducer,
detail: detailReducer
})

export default reducer;

通过connect使用store中数据

detail-index.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
import React, { Component } from 'react';
import { connect } from "react-redux";
import {
DetailWrapper,
Header,
Content,
} from "./style";

class Detail extends Component {
render() {
return (
<DetailWrapper>
<Header>{this.props.title}</Header>
<Content dangerouslySetInnerHTML={{ __html: this.props.content }}></Content>
</DetailWrapper>
)
}
}

const mapStore = (state) => ({
title: state.getIn(["detail", "title"]),
content: state.getIn(["detail", "content"]),
})

export default connect(mapStore, null)(Detail);

使用dangerouslySetInnerHTML属性

  • dangerouslySetInnerHTML属性可使字符串中的html代码解析为html样式
  • 在react中,通过富文本编辑器进行操作后的内容,字符串中的html代码并不能正确展示:
    • 如果我们直接使用content
    • 效果是这样的:效果是这样的
  • 使用dangerouslySetInnerHTML以后:使用dangerouslySetInnerHTML
    • 效果如下:效果
  • 原理
    1. dangerouslySetInnerHTMl 是React组件的一个属性,类似于angular的ng-bind;
    2. 有2个{},第一个{}代表jsx语法开始,第二个{}是代表dangerouslySetInnerHTML接收的是一个对象键值对;
    3. 既可以插入DOM,又可以插入字符串
    4. 不合时宜的使用 innerHTML 可能会导致 cross-site scripting (XSS) 攻击。 净化用户的输入来显示的时候,经常会出现错误,不合适的净化也是导致网页攻击的原因之一。dangerouslySetInnerHTML 这个 prop 的命名是故意这么设计的,以此来警告,它的 prop 值( 一个对象而不是字符串 )应该被用来表明净化后的数据。

异步获取数据

  • 之前我们将数据写死在state中,可实际上应该在Detail组件中组件加载完毕以后通过AJAX请求获取到接口的数据放置在state中
  • 所以我们应该在**componentDidMount()函数中通过一个action发送一个AJAX请求**。
  • action应该在actionCreators.js中统一创建,且还要新建一个action用于将接口中获取到的数据放置在state中
  • 涉及派发action的操作都应该放在connect的参数2中进行,所以我们要在connect的参数2中创建一个函数用于派发action,然后在componentDidMount函数中调用此函数来发送AJAX请求

detail-index.js:

1
import { actionCreators } from './store';

detail-index.js

模拟接口数据

public-api-新建detail.json模拟接口数据:

1
2
3
4
5
6
7
{
"success": true,
"data": {
"title": "疫情就要结束了!",
"content": "<img src='https://i0.wp.com/ww1.sinaimg.cn/large/005H7IVsgy1gcuniztyfhj30dw09tai6.jpg' alt='疫情就要结束了!' /><p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p><p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p><p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p><p>因为一个人,人民摘掉口罩的愿望可能成为了泡影,学生返校,工人正常复工的时间可能又要推迟。一个人的不重视,就可能会导致全国人民努力白费。我不知道为...</p>"
}
}

注意:JSON中不能使用模板字符串,值必须是 双引号 包裹,所以为避免冲突,标签内部引号皆使用单引号。

创建action,发送AJAX请求,获取数据传给store

detail-store-constants.js:

1
export const CHANGE_DETAIL = "detail/CHANGE_DETAIL";

detail-store-actionCreators.js:

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

const changeDetail = (title, content) => {
return {
type: constants.CHANGE_DETAIL,
title,
content
}
}

export const getDetail = () => {
return (dispatch) => {
axios.get("/api/detail.json").then((res) => {
const result = res.data.data;
dispatch(changeDetail(result.title, result.content))
})
}
}

注意:dispatch()会被传递到actionCreators的getDetail函数中,在该函数中也可以使用dispatch()来给store传递数据

reducer处理action,改变state数据

detail-store-reducer.js:
detail-store-reducer.js
正常显示:
正常显示


页面路由参数的传递

  • 存在问题:此时虽然detail页面正常显示,但不管点击home页面的哪一篇文章,进入的都是同一个detail页面
  • 实际上我们应该是点击不同的博客产生的页面路由参数是不同的,然后我们在发送AJAX请求的时候带上对应的路由参数(比如id),这样后端就可以根据不同的路由参数来分配不同的数据(页面)
  • 实现效果:在home页点击某一条博客时,将这条博客的id带给detail页,detail页再在AJAX请求中带上id,后端根据id来分配不同的数据。
    1. home下的list.js中,点击<Link>组件时访问的to属性中带上item的id作为路由参数
    2. App.js中<Route>组件匹配上路径后,**跳转相应页面(detail页面)**。
    3. detail-index.js中,根据this.props拿到参数id将参数id传给发出AJAX请求的action
    4. actionCreators.js中,发出的AJAX请求获取的文件地址中带上参数id,这样后端就能根据请求地址中不同的id回传不同的数据了。
  • 建议使用动态路由的方法进行路由参数的传递,比较简单。

方法1:动态路由

home-component-list.js中可以看到路由地址是写死的:
home-list.js路由地址是写死的

home页传递参数id

使用动态路由使得点击时将id同时传过去
使用动态路由

App.js修改路由匹配规则

此时点击第一条数据可以看到路由带上了id,但访问不到页面了:
效果
原因:我们原本在App.js中规定了要准确匹配才能跳转Detail组件:
App.js
解决方法:path="/detail"改为path="/detail/:id",说明访问detail路径下还要传一个参数名为id的参数
解决方法

detail页获取参数

home页中传参给了detail页,那么detail页就要 获取参数 来发出 不同地址 的AJAX请求以获取显示不同的页面

  1. detail-index.js中,可以通过this.props.match.params.id拿到父组件(home页)传过来的路有参数id
    • detail-index.js:detail-index.js
    • 可以看到this.props下的match的params中有个id对应的就是路由参数的id:打印内容
  2. detail-index.js中,getDetail方法是用于派发“发送AJAX请求”的action给store的,actionCreators的getDetail方法是发送AJAX请求的函数,所以我们可以将前端获取的id传到getDetail方法中,让请求的接口地址带上这个id,这样后端就能拿到对应博客的id,从而返回不同的内容detail-index.jsactionCreators.js

结果:分别点击第一条内容和第二条内容,可以看到发出的AJAX请求地址是不同的
结果


方法2

home页传递参数id

home-component-list.js中,修改跳转的路由地址home-component-list.js

App.js修改路由匹配规则

修改App.js以识别可以跳转Detail组件的路径App.js

detail页获取参数

  1. detail-index.js中找参数id的位置打印
  2. 可以发现id在this.props的loaction的search中,需要进行解析提取出id的值才能使用id位置
  3. 解析步骤就不写了,建议使用“动态路由”,可直接拿到参数,比较简单。
,