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

长风破浪会有时,直挂云帆济沧海