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
-
Link 跳轉無法設置樣式
https://reacttraining.com/react-router/web/api/Link -
NavLink 可以給選中的項設置樣式
https://reacttraining.com/react-router/web/api/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