react默認建立的項目配置文件在 node_modules/react-scripts 文件夾內部css
cnpm run ejecthtml
cnpm run startvue
打開config/webpack.config.js,ctrl + f 搜索 alias,添加配置node
alias: { 'react-native': 'react-native-web', // ++++++++++++++++++++++++++++++++++++++++++++++++++ '@': path.join(__dirname, '../', 'src'), ...(isEnvProductionProfile && { 'react-dom$': 'react-dom/profiling', 'scheduler/tracing': 'scheduler/tracing-profiling', }), ...(modules.webpackAliases || {}), },
src
lib
App.js
index.js
logo.svg
serviceWorker.js
main.scssreact
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();
import React from 'react'; function App() { return ( <div className="App"> </div> ); } export default App;
@import '@/lib/reset.scss'; html { @include background-color(#f66); }
jsx中的class須要寫成classNamewebpack
App.jsios
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;
編寫樣式es6
@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); } }
views/home/index.jsxweb
views/kind/index.jsxnpm
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;
import React from 'react'; import Home from '@/views/home'; // +++++++++++++++++ function App() { return ( <div className="container"> { // +++++++++++++++++ } <Home /> <footer className="footer"></footer> </div> ); } export default App;
入口找佈局,佈局找頁面,頁面找組件
https://reacttraining.com/react-router/web/guides/quick-start
cnpm i react-router-dom -S
之前 react-router
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();
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;
<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); } } } }
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); } } } }
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
引入 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>
cnpm i antd-mobile -S
cnpm i babel-plugin-import -D
"babel": { "presets": [ "react-app" ], // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ "plugins": [ ["import", { "libraryName": "antd-mobile", "style": "css" }] ] },
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': '' } })) }
cnpm i axios -S
utils/request.js + utils/api.js
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; }
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;
@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(); } } }
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;
// 設置變量,使用三木運算符斷定 用戶是否是登錄狀態 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 }) }
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>
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) }) }) } // 三、暴露接口 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 }) }) }
id: LTAIZQoVVoPuBjU9
secret: GfJuI2dLsCQh7Q56TmFxPTniXjkVnB
cnpm i @alicloud/pop-core -S