美文网首页
React 16.4 开发简书项目[9]

React 16.4 开发简书项目[9]

作者: Mark同学 | 来源:发表于2019-12-04 16:53 被阅读0次

    第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));
    

    10-1 项目上线流程

    XAMPP

    api => htdocs

    npm run build

    复制 build 下的文件 => htdocs

    localhost:8888

    https://github.com/wtyqer/react-jian-login

    相关文章

      网友评论

          本文标题:React 16.4 开发简书项目[9]

          本文链接:https://www.haomeiwen.com/subject/ffylgctx.html