第9章 项目实战:详情页面和登录功能开发
9-1 详情页面布局
src
——pages
————detail
——————style.js
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;
}
b {
font-weight: bold;
}
`;
src
——pages
————detail
——————index.js
import React from 'react';
import { DetailWrapper, Header, Content } from './style';
function Detail() {
return (
<DetailWrapper>
<Header>衡水中学,被外地人占领的高考工厂</Header>
<Content>
<img src='https://img.haomeiwen.com/i10295326/b7d6641a66c7fafc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/524' alt=''/>
<p><b>2017年,衡水中学考上清华北大的人数是176人</b>,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p>
</Content>
</DetailWrapper>
);
}
export default Detail;
9-2 使用redux管理详情页面数据
1.
src
——pages
————detail
——————store
————————reducer.js
import { fromJS } from 'immutable';
import * as constants from './constants';
const defaultState = fromJS({
title: '衡水中学,被外地人占领的高考工厂',
content: '<img src="https://img.haomeiwen.com/i10295326/b7d6641a66c7fafc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/524" alt=""/><p><b>2017年,衡水中学考上清华北大的人数是176人</b>,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p>'
});
export default (state = defaultState, action) => {
switch(action.type) {
default:
return state;
}
};
src
——pages
————detail
——————store
————————index.js
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer, actionCreators, constants };
2.mapState 拿数据
src
——pages
————detail
——————index.js
import { connect } from 'react-redux';
function Detail(props) {
// ...
<DetailWrapper>
<Header>{props.title}</Header>
<Content dangerouslySetInnerHTML={{__html: props.content}} />
</DetailWrapper>
//...
}
const mapState = (state) => ({
title: state.getIn(['detail', 'title']),
content: state.getIn(['detail', 'content'])
})
export default connect(mapState, null)(Detail);
3.
src
———store
————reducer.js
//...
import { reducer as detailReducer} from '../pages/detail/store';
const reducer = combineReducers({
//...
detail: detailReducer
})
export default reducer;
9-3 异步获取数据
src
——pages
————detail
——————index.js
import React, { PureComponent } from 'react';
import { actionCreators } from './store';
//...
class Detail extends PureComponent{
render() {
return (
<DetailWrapper>
<Header>{ this.props.title }</Header>
<Content dangerouslySetInnerHTML={{__html: this.props.content}} />
</DetailWrapper>
)
}
componentDidMount() {
this.props.getDetail();
}
}
const mapState = (state) => ({
//...
})
const mapDispatch = (dispatch) => ({
getDetail() {
dispatch(actionCreators.getDetail());
}
})
export default connect(mapState, mapDispatch)(Detail);
src
——pages
————detail
——————store
————————actionCreators.js
import axios from 'axios';
import * as constants from './constants';
const changeDetail = (title, content) => ({
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));
})
}
}
src
——pages
————detail
——————store
————————constants.js
export const CHANGE_DETAIL = 'detail/CHANGE_DETAIL';
detail.json
{
"success": true,
"data": {
"title": "衡水中学,被外地人占领的高考工厂",
"content": "<img src='https://img.haomeiwen.com/i10295326/b7d6641a66c7fafc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/524'/><p><b>2017年,衡水中学考上清华北大的人数是176人</b>,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p><p>2017年,衡水中学考上清华北大的人数是176人,2016年是139人,再往前推到2015年,这个人数是119人。但是在这些辉煌的名单背后,却是外地来衡水上学人数暴涨,本地人上好高中越来越艰难的尴尬处境。</p>"
}
}
src
——pages
————detail
——————store
————————reducer.js
import { fromJS } from 'immutable';
import * as constants from './constants';
const defaultState = fromJS({
title: '',
content: ''
});
export default (state = defaultState, action) => {
switch(action.type) {
case constants.CHANGE_DETAIL:
return state.merge({
title: action.title,
content: action.content
})
default:
return state;
}
};
9-4 页面路由参数的传递
.动态路由-获取参数
src
——pages
————home
——————components
————————list.js
<Link key={index} to={'/detail/' + item.get('id')}>
src
——App.js
<Route path='/detail/:id' exact component={Detail}></Route>
src
——pages
————detail
——————index.js
//...
componentDidMount() {
this.props.getDetail(this.props.match.params.id);
}
//...
const mapDispatch = (dispatch) => ({
getDetail(id) {
dispatch(actionCreators.getDetail(id));
}
})
src
——pages
————detail
——————store
————————actionCreators.js
//...
export const getDetail = (id) => {
return (dispatch) => {
axios.get('/api/detail.json?id=' + id).then((res) => {
//...
})
}
}
9-5 登陆页面布局
src
——pages
————login
——————index.js
import React from 'react';
import { connect } from 'react-redux';
import { LoginWrapper, LoginBox, Input, Button } from './style';
function Login () {
return (
<LoginWrapper>
<LoginBox>
<Input placeholder='账号'/>
<Input placeholder='密码'/>
<Button>登录</Button>
</LoginBox>
</LoginWrapper>
)
}
const mapState = (state) => ({
});
const mapDispatch = (dispatch) => ({
});
export default connect(mapState, mapDispatch)(Login);
src
——App.js
import Login from './pages/login';
//...
<Route path='/login' exact component={Login}></Route>
src
——pages
————login
——————style.js
import styled from 'styled-components';
export const LoginWrapper = styled.div`
z-index: 0;
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 56px;
background: #eee;
`;
export const LoginBox = styled.div`
width: 400px;
height: 180px;
margin: 100px auto;
padding-top: 20px;
background: #fff;
box-shadow: 0 0 8px rgba(0,0,0,.1);
`;
export const Input = styled.input`
display: block;
width: 200px;
height: 30px;
line-height: 30px;
padding: 0 10px;
margin: 10px auto;
color: #777;
`;
export const Button = styled.div`
width: 220px;
height: 30px;
line-height: 30px;
color: #fff;
background: #3194d0;
border-radius: 15px;
margin: 20px auto;
text-align: center;
cursor: pointer;
`;
9-6 登陆功能实现
1.创建 Store + 4
src
——pages
————login
——————store
————————index.js
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer, actionCreators, constants };
初始化 login 状态
————————reducer.js
import { fromJS } from 'immutable';
const defaultState = fromJS({
login: false
});
export default (state = defaultState, action) => {
switch(action.type) {
default:
return state;
}
};
————————actionCreators.js
————————constants.js
2.
src
——store
————reducer.js
//...
import { reducer as loginReducer } from '../pages/login/store';
//...
login: loginReducer
3.改变首页 Header 登录样式
src
——common
————header
——————index.js
import { actionCreators as loginActionCreators } from '../../pages/login/store';
//...
{
props.login ?
<NavItem onClick={props.logout} className='right'>退出</NavItem> :
<Link to='/login'><NavItem className='right'>登录</NavItem></Link>
}
//...
const mapStateToProps = (state) => {
return {
//...
login: state.getIn(['login', 'login']);
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleInputFocus() {
dispatch(actionCreators.searchFocus());
},
handleInputBlur() {
dispatch(actionCreators.searchBlur());
},
logout() {
dispatch(loginActionCreators.logout());
}
}
}
//...
4.实现登录的逻辑
- AJAX 调用登录接口把账号密码传递过去
- innerRef 的写法已经废弃
- extends 组件内才可以用 this.XX 保存值
- render 内才能使用if 判断
src
——pages
————login
——————index.js
import React, {PureComponent} from 'react';
import { actionCreators } from './store';
//...
class Login extends PureComponent {
render() {
return (
<LoginWrapper>
<LoginBox>
<Input placeholder='账号' ref={(input) => {this.account = input}}/>
<Input placeholder='密码' type='password' ref={(input) => {this.password = input}}/>
<Button onClick={() => this.props.login(this.account, this.password)}>登录</Button>
</LoginBox>
</LoginWrapper>
)
}
}
const mapDispatch = (dispatch) => ({
login(accountEl, passwordEl) {
dispatch(actionCreators.login(accountEl.value, passwordEl.value));
}
});
export default connect(null, mapDispatch)(Login);
5.发送登录的 AJAX 请求
src
——pages
————login
——————store
————————actionCreators.js
import axios from 'axios';
import * as constants from './constants';
const changLogin = () => ({
type: constants.CHANGE_LOGIN,
value: true
})
export const login = (account, password) => {
return (dispatch) => {
axios.get('/api/login.json?account=' + account + '&password=' + password).then((res) => {
const result = res.data.data;
if(result) {
dispatch(changLogin())
} else {
alert('登录失败');
}
})
}
}
export const logout = () => ({
type: constants.LOGOUT,
value: false
})
login.json
{
"success": true,
"data": true
}
src
——pages
————login
——————store
————————constants.js
export const CHANGE_LOGIN = 'login/CHANGE_LOGIN';
export const LOGOUT = 'login/LOGOUT';
src
——pages
————login
——————store
————————reducer.js
import { fromJS } from 'immutable';
import * as constants from './constants';
const defaultState = fromJS({
login: false
});
export default (state = defaultState, action) => {
switch(action.type) {
case constants.CHANGE_LOGIN:
return state.set('login', action.value);
case constants.LOGOUT:
return state.set('login', action.value);
default:
return state;
}
};
判断登录成功后跳转首页
完整的实现
src
——pages
————login
——————index.js
import React, {PureComponent} from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { actionCreators } from './store';
import { LoginWrapper, LoginBox, Input, Button } from './style';
class Login extends PureComponent {
render() {
if (!this.props.loginStatus) {
return (
<LoginWrapper>
<LoginBox>
<Input placeholder='账号' ref={(input) => {this.account = input}}/>
<Input placeholder='密码' type='password' ref={(input) => {this.password = input}}/>
<Button onClick={() => this.props.login(this.account, this.password)}>登录</Button>
</LoginBox>
</LoginWrapper>
)
} else {
return <Redirect to='/' />
}
}
}
const mapState = (state) => ({
loginStatus: state.getIn(['login', 'login'])
})
const mapDispatch = (dispatch) => ({
login(accountEl, passwordEl) {
dispatch(actionCreators.login(accountEl.value, passwordEl.value));
}
});
export default connect(mapState, mapDispatch)(Login);
9-7 登陆鉴权及代码优化
1.
src
——pages
————write
——————index.js
import React, {PureComponent} from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
class Write extends PureComponent {
render() {
if (!this.props.loginStatus) {
return (
<div>写文章页面</div>
)
} else {
return <Redirect to='/login' />
}
}
}
const mapState = (state) => ({
loginStatus: state.getIn(['login', 'login'])
})
export default connect(mapState, null)(Write);
2.
src
——App.js
import Write from './pages/write';
<Route path='/write' exact component={Write}></Route>
3.
src
——common
————header
——————index.js
<Link to='/write'>写文章<Link>
4.优化
- 回车换行
- 分号
- 函数封装
- .catch(() => {})
- 功能扩展
9-8 异步组件及withRouter路由方法的使用
按需加载
npm i react-loadable
https://github.com/jamiebuilds/react-loadable
import Loadable from 'react-loadable';
import Loading from './my-loading-component';
const LoadableComponent = Loadable({
loader: () => import('./my-component'),
loading: Loading,
});
export default class App extends React.Component {
render() {
return <LoadableComponent/>;
}
}
detail
——loadable.js
import React from 'react';
import Loadable from 'react-loadable';
const LoadableComponent = Loadable({
loader: () => import('./'),
loading() {
return <div>正在加载</div>
}
});
export default () => <LoadableComponent/>;
src
——App.js
import Detail from './pages/detail/loadable.js';
src
——pages
————detail
——————index.js
import { withRouter } from 'react-router-dom';
//...
export default connect(mapState, mapDispatch)(withRouter(Detail));
网友评论