react抽离配置文件、配置@符号、调整src文件夹---配置scss、编写项目的页面结构、创建各个页面 src/views、开始路由、入口文件处修改代码、修改App.js布局文件、添加底部的导航布局、构建个人中心。。。声明式跳转路由、使用React UI库请求渲染首页数据、

1、回顾

2、react项目的配置

react默认创建的项目配置文件在 node_modules/react-scripts 文件夹内部

2.1 抽离配置文件

cnpm run eject

cnpm run start

2.2 配置@符号

打开config/webpack.config.js,ctrl + f 搜索 alias,添加配置

alias: {
  'react-native': 'react-native-web',
  // ++++++++++++++++++++++++++++++++++++++++++++++++++
  '@': path.join(__dirname, '../', 'src'),
  ...(isEnvProductionProfile && {
    'react-dom$': 'react-dom/profiling',
    'scheduler/tracing': 'scheduler/tracing-profiling',
  }),
  ...(modules.webpackAliases || {}),
},

2.3 调整src文件夹---配置scss

src
lib
App.js
index.js
logo.svg
serviceWorker.js
main.scss

  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './main.scss'; // +++++++++++++++++++++++++++++++
import App from '@/App';
import * as serviceWorker from '@/serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

  • App.js
import React from 'react';

function App() {
  return (
    <div className="App">
     
    </div>
  );
}

export default App;

  • main.scss
@import '@/lib/reset.scss';

html {
  @include background-color(#f66);
}

3、编写项目的页面结构

jsx中的class需要写成className

App.js

import React from 'react';

function App() {
  return (
    <div className="container">
      <div className="box">
        <header className="header"></header>
        <div className="content"></div>
      </div>
      <footer className="footer"></footer>
    </div>
  );
}

export default App;

编写样式

@import '@/lib/reset.scss';

html, body, #root, .container {
  @include rect(100%, 100%);
}

.container {
  @include flexbox();
  @include flex-direction(column);
  .box {
    @include flex();
    @include rect(100%, auto);
    @include flexbox();
    @include flex-direction(column);
    .header {
      @include rect(100%, 0.44rem);
      @include background-color(#f66);
    }
    .content {
      @include flex();
      @include rect(100%, auto);
      @include overflow();
    }
  }
  .footer {
    @include rect(100%, 0.5rem);
    @include background-color(#efefef);
  }
}

4、创建各个页面 src/views

views/home/index.jsx

views/kind/index.jsx

views/cart/index.jsx

views/user/index.jsx

以首页为例

import React from 'react';

class Com extends React.Component {
  render () {
    return (
      <div className="box">
        <header className="header">首页头部</header>
        <div className="content">首页内容</div>
      </div>
    )
  }
}

export default Com;
  • 测试页面模块 App.js
import React from 'react';
import Home from '@/views/home'; // +++++++++++++++++
function App() {
  return (
    <div className="container">
      {
        // +++++++++++++++++
      }
      <Home />
      <footer className="footer"></footer>
    </div>
  );
}

export default App;

5、开始路由

入口找布局,布局找页面,页面找组件

https://reacttraining.com/react-router/web/guides/quick-start

cnpm i react-router-dom -S

**以前 react-router **

5.1 入口文件处修改代码

import React from 'react';
import ReactDOM from 'react-dom';
import './main.scss';
import App from '@/App';
import * as serviceWorker from '@/serviceWorker';
// HashRouter ---- vue中的hash   /#/home
// BrowserRouter ---- vue中的history /home
//  BrowserRouter as Router 把BrowserRouter起名为Router
// Route 路由
// Switch多个只能选中一个  ------ BrowserRouter 只能有一个子元素
// 一个应用有很多布局 --- Switch
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';

// 入口找布局,布局找页面,页面找组件
// App 就是一个布局文件
ReactDOM.render(
  <Router>
    <Switch>
      <Route path="/">
        <App />
      </Route>
    </Switch>
  </Router>
  , document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

5.2 修改App.js布局文件

import React from 'react';
import Home from '@/views/home';
import Kind from '@/views/kind';
import Cart from '@/views/cart';
import User from '@/views/user';
// 一种布局有很多的页面 --- Switch
import { Switch, Route } from 'react-router-dom';
// 布局找页面
function App() {
  return (
    <div className="container">
      <Switch>
        <Route path = "/home"><Home /></Route>
        <Route path = "/kind"><Kind /></Route>
        <Route path = "/cart"><Cart /></Route>
        <Route path = "/user" component = { User }/ >
        {
          //<Route path = "/user"><User /></Route>
        }
      </Switch>
      <footer className="footer"></footer>
    </div>
  );
}

export default App;

6、添加底部的导航布局

<footer className="footer">
  <ul>
    <li>
      <span className="iconfont icon-fonts-shouye"></span>
      <p>首页</p>
    </li>
    <li>
      <span className="iconfont icon-icon"></span>
      <p>分类</p>
    </li>
    <li>
      <span className="iconfont icon-gouwuche"></span>
      <p>购物车</p>
    </li>
    <li>
      <span className="iconfont icon-wode"></span>
      <p>我的</p>
    </li>
  </ul>
</footer> 

main.scss

.footer {
  @include rect(100%, 0.5rem);
  @include background-color(#efefef);
  ul {
    @include rect(100%, 100%);
    @include flexbox();
    li {
      @include flex();
      @include rect(auto, 100%);
      @include flexbox();
      @include flex-direction(column);
      @include justify-content();
      @include align-items();
      span {
        @include font-size(0.24rem);
      }
      p {
        @include font-size(0.12rem);
      }
    }
  }
}

7、声明式跳转路由

Link / NavLink

App.js ---- NavLink标签会自动解析为 a标签,需要更改样式表,

通过activeClassName给选中的路由添加样式

// NavLink 必须导入
import { Switch, Route, NavLink } from 'react-router-dom';

<footer className="footer">
  <ul>
    <NavLink to="/home" activeClassName="active">
      <span className="iconfont icon-fonts-shouye"></span>
      <p>首页</p>
    </NavLink>
    <NavLink to="/kind" activeClassName="active">
      <span className="iconfont icon-icon"></span>
      <p>分类</p>
    </NavLink>
    <NavLink to="/cart" activeClassName="active">
      <span className="iconfont icon-gouwuche"></span>
      <p>购物车</p>
    </NavLink>
    <NavLink to="/user" activeClassName="active">
      <span className="iconfont icon-wode"></span>
      <p>我的</p>
    </NavLink>
  </ul>
</footer>

main.scss

.footer {
  @include rect(100%, 0.5rem);
  @include background-color(#efefef);
  ul {
    @include rect(100%, 100%);
    @include flexbox();
    a { // +++++++++++++++++++++++++
      @include color(#666);
      @include flex();
      @include rect(auto, 100%);
      @include flexbox();
      @include flex-direction(column);
      @include justify-content();
      @include align-items();
      span {
        @include font-size(0.24rem);
      }
      p {
        @include font-size(0.12rem);
      }
      &.active { // ++++++++++++++++++++++++++
        @include color(#f66);
      }
    }
  }
}

8、使用React UI库

PC: element-ui 、 ant design (antd)

移动端: ant design mobile (antd-mobile)

https://mobile.ant.design/index-cn

https://mobile.ant.design/docs/react/use-with-create-react-app-cn

8.1 修改public/index.html

引入 FastClick 并且设置 html meta (更多参考 #576)

引入 Promise 的 fallback 支持 (部分安卓手机不支持 Promise)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
  <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
  <script>
    if ('addEventListener' in document) {
      document.addEventListener('DOMContentLoaded', function() {
        FastClick.attach(document.body);
      }, false);
    }
    if(!window.Promise) {
      document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
    }
  </script>
    <link rel="apple-touch-icon" href="logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
    <link rel="stylesheet" href="//at.alicdn.com/t/font_1476238_uph8zgimp3.css">
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

8.2 安装模块

cnpm i antd-mobile -S

cnpm i babel-plugin-import -D

8.3 修改package.json文件中的babel选项

"babel": {
  "presets": [
    "react-app"
  ],
  // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  "plugins": [
    ["import", { "libraryName": "antd-mobile", "style": "css" }]
  ]
},

9、请求渲染首页数据

9.1 解决跨域问题

cnpm i http-proxy-middleware -D

src/setupProxy.js

const proxy = require('http-proxy-middleware');

module.exports = function (app) {
  app.use(proxy('/api', {
    target: 'http://47.92.152.70', // 代理哪一个服务器
    changeOrigin: true, // 代理
    pathRewrite: {
      '^/api': '' // 以 /api 开头的请求,认为就是请求的代理
      // /api/pro   ===> http://47.92.152.70/pro
    }
  }))
  // 纯属凑数
  app.use(proxy('/test', {
    target: 'https://www.baidu.com', // 代理哪一个服务器
    changeOrigin: true, // 代理
    pathRewrite: {
      '^/test': ''
    }
  }))
}

9.2 封装数据请求模块

cnpm i axios -S

utils/request.js + utils/api.js

9.3 首页使用UI库的 走马灯 ---- 轮播图

https://mobile.ant.design/components/carousel-cn/

import React from 'react';
// ++++++++++++++++++++++++++++++++++++++++++++
import { Carousel } from 'antd-mobile';
// +++++++++++++++++++++++++++++++++++++++++++++++++
import { getBannerlist, getProlist } from '@/utils/api';
class Com extends React.Component {
  constructor (props) {
    super(props);
    this.state = { // +++++++++++++++++++++++++++++++++++++++++
      bannerlist: [{ bannerid: 1, img: 'images/1.jpg'}],
      prolist: []
    }
  }

  componentDidMount () { // +++++++++++++++++++++++++++++++++++
    getBannerlist().then(data => {
      console.log(data.data)
      this.setState({
        bannerlist: data.data
      })
    })
    getProlist().then(data => {
      this.setState({
        prolist: data.data
      })
    })
  }

  render () {
    return (
      <div className="box">
        <header className="header">首页头部</header>
        <div className="content">
          {
            // +++++++++++++++++++++++++++
          }
          <Carousel
            autoplay={ true }
            infinite
            beforeChange={(from, to) => console.log(`slide from ${from} to ${to}`)}
            afterChange={index => console.log('slide to', index)}
          >
            {this.state.bannerlist.map(item => (
              <a
                key={ item.bannerid }
                href="https://www.baidu.com"
                style={{ display: 'inline-block', width: '100%', height: '176px' }}
              >
                <img
                  src={`http://47.92.152.70/${item.img}`}
                  alt=""
                  style={{ width: '100%', verticalAlign: 'top' }}
                  onLoad={() => {
                    // fire window resize event to change height
                    window.dispatchEvent(new Event('resize'));
                    this.setState({ imgHeight: 'auto' });
                  }}
                />
              </a>
            ))}
          </Carousel>
        </div>
      </div>
    )
  }
}

export default Com;

main.scss中添加

* { touch-action: none; }

9.4 封装Prolist.jsx组件,首页列表的渲染

  • components/Prolist/index.jsx
import React from 'react';
import './style.scss';
// class组件获取数据 使用 this.props
// 函数式组件含有默认的参数 props   ----- 等同与 class 组件中的this.props
const Com = (props) => {
  return (
    <ul className="prolist">
      <li className="proitem">
        <div className="proimg">
          <img src="" alt="" />
        </div>
        <div className="proinfo">
          111111111
        </div>
      </li>
    </ul>
  )
}

export default Com;

  • components/Prolist/style.scss
@import '@/lib/reset.scss';
.prolist {
  @include rect(100%, auto);
  .proitem {
    @include rect(100%, 1rem);
    @include border(0 0 1px 0, #efefef, solid); // 设定的是一个物理像素
    @include flexbox();
    .itemimg {
      @include rect(1rem, 1rem);
      img {
        @include rect(0.9rem, 0.9rem);
        @include border(1px, #f66, solid);
        @include margin(0.05rem);
        @include display(block);
      }
    }
    .iteminfo {
      @include flex();
    }
  }
}
  • 首页面引入 Prolist 组件
import Prolist from '@/components/Prolist';
<Prolist />
  • 首页传值给列表
<Prolist prolist = { this.state.prolist }/>
  • 列表组件渲染数据
import React from 'react';
import './style.scss';
// class组件获取数据 使用 this.props
// 函数式组件含有默认的参数 props   ----- 等同与 class 组件中的this.props
const Com = (props) => {
  return (
    <ul className="prolist">
      {
        props.prolist.map(item => {
          return (
            <li className="proitem" key = { item.proid }>
              <div className="proimg">
                <img src={ item.proimg } alt="" />
              </div>
              <div className="proinfo">
                { item.proname }
              </div>
            </li>
          )
        })
      }
    </ul>
  )
}

export default Com;

10、构建个人中心

// 设置变量,使用三木运算符判定 用户是不是登陆状态
import React from 'react';

class Com extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      flag: false
    }
  }
  render () {
    return (
      <div className="box">
        <header className="header">个人中心头部</header>
        <div className="content">
          {
            this.state.flag ? 
            <div>
              欢迎您......
            </div>
            :
            <div>
              <button>登陆</button>
              <button>注册</button>
            </div>
          }
        </div>
      </div>
    )
  }
}

export default Com;
  • 判定用户有没有登陆
componentDidMount () {
    // if (localStorage.getItem('isLogin') === 'ok') {
    //   this.setState({
    //     flag: true
    //   })
    // } else {
    //    this.setState({
    //      flag: false
    //    })
    // }
    let flag = localStorage.getItem('isLogin') === 'ok' ? true : false
    this.setState({
      flag
    })
  }
  • 给登陆按钮设置点击事件,跳转到登陆页面

11、构建登陆页面

views/login/index.jsx

import React from 'react';

class Com extends React.Component {
  render () {
    return (
      <div className="box">
        <header className="header">登陆</header>
        <div className="content">登陆</div>
      </div>
    )
  }
}

export default Com;

入口找布局,布局找页面,页面找组件 --- 登陆没有底部的页面布局

  • 添加新的布局文件

src/Other.js

import React from 'react';
import Login from '@/views/login';
import { Switch, Route } from 'react-router-dom';
// 布局找页面 /o/login --- 自定义  o 表示新的布局
function Other () { 
  return (
    <div className="container">
      <Switch>
        <Route path="/o/login"><Login /></Route>
      </Switch>
    </div>
  );
}

export default Other;

  • 入口添加新的布局 / 所对应的布局在最下面
import React from 'react';
import ReactDOM from 'react-dom';
import './main.scss';
import App from '@/App';
import Other from '@/Other'; // 新的布局文件 +++++++++++++++++++++++++++++++
import * as serviceWorker from '@/serviceWorker';
import { HashRouter as Router, Switch, Route } from 'react-router-dom';

// 入口找布局,布局找页面,页面找组件
// App 就是一个布局文件
/**
<Route path="/o">
  <Other />
</Route>
*/

ReactDOM.render(
  <Router>
    <Switch>
      <Route path="/o">
        <Other />
      </Route>
      <Route path="/">
        <App />
      </Route>
    </Switch>
  </Router>
  , document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
  • 点击登陆 使用编程式跳转 到 登陆页面

this.props.history.push() / replace() / goBack()

// views/user/index.js
<button onClick = { () => {
  // console.log(this.props)
  this.props.history.push('/o/login')
}}>登陆</button>

12 编辑登陆的表单

views/login/index.js

import React from 'react';
import './style.scss'
class Com extends React.Component {
  render () {
    return (
      <div className="box">
        <header className="header">登陆</header>
        <div className="content">
          <input type="text" placeholder="手机号码"/>
          <p className="tip"></p>
          <input type="password" placeholder="密码" />
          <p className="tip"></p>
          <button className="userBtn" >登陆</button>
          <p className="tip"></p>
        </div>
      </div>
    )
  }
}

export default Com;

views/login/style.scss

input {
  outline: none;
  border: 0;
  display: block;
  width: 96%;
  margin: 5px 2%;
  border-bottom: 1px solid #efefef;
  line-height: 36px;
  text-indent: 10px;
}
.tip {
  color: #f66;
  text-align: center;
  height: 20px;
}
.userBtn {
  outline: none;
  border: 0;
  display: block;
  background-color:#f66;
  width: 96%;
  margin: 15px 2%;
  line-height: 40px;
  font-size: 18px;
  color: #fff;
}
  • 检验表单信息 --- 手机号

this.state = { 
  tel: '18813007814',
  telTip: ''
}

<input type="text" placeholder="手机号码" value={ this.state.tel } onChange={ (event) => {
  // 输入框绑定value值
  console.log(event.currentTarget.value)
  // 通过事件对象获取输入框的值
  let val = event.currentTarget.value
  // 提示标识变量
  let tip = ''
  // 输入时,如果输入的值为空,标识为空
  // 如果输入的语法错误,标识为手机号码格式错误
  // 否则 标识为ok
  tip = val === '' ? '' : val.length !== 11 ? '手机号码格式错误' : 'ok'
  // 修改状态 --- 视图二次渲染
  this.setState({
    tel: val,
    telTip: tip
  })
} }/>
<p className="tip">{ this.state.telTip }</p>
  • 校验表单信息 --- 密码
this.state = { 
  tel: '18813007814',
  telTip: '',
  password: '123456',
  passwordTip: ''
}


<input type="password" placeholder="密码" value= { this.state.password } onChange = { this.validPassword.bind(this)}/>
<p className="tip">{ this.state.passwordTip }</p>


validPassword (event) {
  // console.log(event)
  let val = event.currentTarget.value;
  let tip = ''
  tip = val === '' ? '' : val.length < 6 ? '密码格式错误' : ''
  this.setState({
    password: val,
    passwordTip: tip
  })
}
  • 登陆功能
<button className="userBtn" onClick= { this.login.bind(this) }>登陆</button>
  • 封装登陆的接口

utils/api.js

/**
 * 登陆接口
 * @param {tel} String 
 * @param {password} String 
 */
const login = (tel, password) => {
  return new Promise(resolve => {
    request.post('/users/login', { tel, password }).then(res => {
      resolve(res.data)
    })
  })
}

// 3、暴露接口
export {
  getProlist,
  getBannerlist,
  getCartlist,
  login // ++++++++++++++++++++++++
}
  • 实现登陆功能
login () {
  // 点击登陆验证手机号码输入是否正确
  if (this.state.tel === '' || this.state.telTip === '手机号码格式错误') {
    this.setState({
      tip: '请输入合法的电话号码'
    })
    return
  }
  // 点击登陆验证密码输入是否正确
  if (this.state.password === '' || this.state.passwordTip === '密码格式错误') {
    this.setState({
      tip: '请输入合法的密码'
    })
    return
  }
  // 请求接口
  login(this.state.tel, this.state.password).then(data => {
    console.log(data)
    let tip = '' // 显示的是 后端返回的数据的标识
    if (data.code === '10086') {
      tip = '用户未注册,请先注册'
    } else if (data.code === '10100') {
      tip = '密码错误'
    } else {
      tip = '登陆成功'
      localStorage.setItem('token', data.token)
      localStorage.setItem('userid', data.userid)
      localStorage.setItem('username', data.username)
      localStorage.setItem('isLogin', 'ok')
      this.props.history.goBack()
    }
    this.setState({
      tip
    })
  })
}

13、发送短信验证码

1、申请签名和短信模板

2、获取用户标识

id: LTAIZQoVVoPuBjU9

secret: GfJuI2dLsCQh7Q56TmFxPTniXjkVnB

3、编写代码

cnpm i @alicloud/pop-core -S

posted @ 2019-11-19 22:06  菜鸟小何  阅读(982)  评论(0编辑  收藏  举报