详情页面布局
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代码并不能正确展示:
- 效果是这样的:
- 使用dangerouslySetInnerHTML以后:
- 效果如下:
- 原理:
- dangerouslySetInnerHTMl 是React组件的一个属性,类似于angular的ng-bind;
- 有2个
{}
,第一个{}
代表jsx语法开始,第二个{}
是代表dangerouslySetInnerHTML接收的是一个对象键值对;
- 既可以插入DOM,又可以插入字符串;
- 不合时宜的使用 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';
|
模拟接口数据
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页面正常显示,但不管点击home页面的哪一篇文章,进入的都是同一个detail页面。
- 实际上我们应该是点击不同的博客产生的页面路由参数是不同的,然后我们在发送AJAX请求的时候带上对应的路由参数(比如id),这样后端就可以根据不同的路由参数来分配不同的数据(页面)。
- 实现效果:在home页点击某一条博客时,将这条博客的id带给detail页,detail页再在AJAX请求中带上id,后端根据id来分配不同的数据。
- home下的list.js中,点击
<Link>
组件时访问的to
属性中带上item的id作为路由参数
- App.js中
<Route>
组件匹配上路径后,**跳转相应页面(detail页面)**。
- detail-index.js中,根据
this.props
拿到参数id,将参数id传给发出AJAX请求的action。
- actionCreators.js中,发出的AJAX请求获取的文件地址中带上参数id,这样后端就能根据请求地址中不同的id回传不同的数据了。
- 建议使用动态路由的方法进行路由参数的传递,比较简单。
方法1:动态路由
home-component-list.js中可以看到路由地址是写死的:
home页传递参数id
使用动态路由使得点击时将id同时传过去:
App.js修改路由匹配规则
此时点击第一条数据可以看到路由带上了id,但访问不到页面了:
原因:我们原本在App.js中规定了要准确匹配才能跳转Detail组件:
解决方法:将path="/detail"
改为path="/detail/:id"
,说明访问detail路径下还要传一个参数名为id的参数:
detail页获取参数
home页中传参给了detail页,那么detail页就要 获取参数 来发出 不同地址 的AJAX请求以获取显示不同的页面。
- detail-index.js中,可以通过
this.props.match.params.id
拿到父组件(home页)传过来的路有参数id。
- detail-index.js:
- 可以看到this.props下的match的params中有个id对应的就是路由参数的id:
- detail-index.js中,getDetail方法是用于派发“发送AJAX请求”的action给store的,actionCreators的getDetail方法是发送AJAX请求的函数,所以我们可以将前端获取的id传到getDetail方法中,让请求的接口地址带上这个id,这样后端就能拿到对应博客的id,从而返回不同的内容:
结果:分别点击第一条内容和第二条内容,可以看到发出的AJAX请求地址是不同的:
方法2
home页传递参数id
home-component-list.js中,修改跳转的路由地址:
App.js修改路由匹配规则
修改App.js以识别可以跳转Detail组件的路径
detail页获取参数
- detail-index.js中找参数id的位置:
- 可以发现id在this.props的loaction的search中,需要进行解析提取出id的值才能使用:
- 解析步骤就不写了,建议使用“动态路由”,可直接拿到参数,比较简单。