首先附上地址 react 移动端
1. 创建项目
npx create-react-app my-app
2. 暴露配置项
npm run eject
3. 引入flexible.js文件
// index.js
import './flexible.js'
// flexible.js
(function(win, lib) {
var doc = win.document;
var docEl = doc.documentElement;
var metaEl = doc.querySelector('meta[name="viewport"]');
var flexibleEl = doc.querySelector('meta[name="flexible"]');
var dpr = 0;
var scale = 0;
var tid;
var flexible = lib.flexible || (lib.flexible = {});
if (metaEl) {
console.warn('将根据已有的meta标签来设置缩放比例');
var match = metaEl.getAttribute('content').match(/initial-scale=([\d.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
} else if (flexibleEl) {
var content = flexibleEl.getAttribute('content');
if (content) {
var initialDpr = content.match(/initial-dpr=([\d.]+)/);
var maximumDpr = content.match(/maximum-dpr=([\d.]+)/);
if (initialDpr) {
dpr = parseFloat(initialDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximumDpr) {
dpr = parseFloat(maximumDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
if (!dpr && !scale) {
// var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下,仍旧使用1倍的方案
dpr = 1;
}
scale = 1 / dpr;
}
docEl.setAttribute('data-dpr', dpr);
if (!metaEl) {
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement('div');
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
function refreshRem(){
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
width = 540 * dpr;
}
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}
win.addEventListener('resize', function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener('pageshow', function(e) {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === 'complete') {
doc.body.style.fontSize = 12 * dpr + 'px';
} else {
doc.addEventListener('DOMContentLoaded', function(e) {
doc.body.style.fontSize = 12 * dpr + 'px';
}, false);
}
refreshRem();
flexible.dpr = win.dpr = dpr;
flexible.refreshRem = refreshRem;
flexible.rem2px = function(d) {
var val = parseFloat(d) * this.rem;
if (typeof d === 'string' && d.match(/rem$/)) {
val += 'px';
}
return val;
}
flexible.px2rem = function(d) {
var val = parseFloat(d) / this.rem;
if (typeof d === 'string' && d.match(/px$/)) {
val += 'rem';
}
return val;
}
})(window, window['lib'] || (window['lib'] = {}));
4. 安装postcss-flexible插件
npm i postcss-flexible
// webpack.config.js
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
// 使用postcss-flexible插件
require('postcss-flexible')({remUnit: 37.5}),
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
postcssNormalize(),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
5. 使用.styl
不喜欢用styl的此步可以省略
npm i stylus stylus-loader
...
postcssNormalize(),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
{
loader: 'stylus-loader',
options: {
sourceMap: true,
}
},
...
'sass-loader'
),
},
// 使用.styl文件
{
test: /\.styl$/,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
sideEffects: true,
},
{
loader: require.resolve('file-loader'),
// 添加styl文件的解析
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/, /\.styl$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
使用
//index.styl
.footer {
width: 100%;
height: rem(60px);
&-item {
color: red;
}
}
// index.js
import './index.styl'
<div className="footer">
<div className="footer-item>home</div>
</div>
6. 使用.scss
npm i node-sass sass-loader
使用
// index.module.scss
.box {
width: 100%;
height: rem(60px);
}
.font {
color: red;
}
// index.js
import styles from './index.module.scss'
<div className={`${styles['box']} ${styles['font']}`}>car</div>
7. 添加redux
npm i react-redux redux redux-persist redux-thunk
在index.js引入
// index.js
// redux
import {Provider} from 'react-redux'
import {PersistGate} from 'redux-persist/integration/react'
import redux from './store'
const {store, persistor} = redux()
ReactDOM.render(
<Provider store={store}>
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root')
)
新建store文件夹
// store/index.js
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import { persistStore, persistReducer } from 'redux-persist'
import storageSession from 'redux-persist/lib/storage/session'
import reducer from './reducer'
const persistConfig = {
key: 'root',
storage: storageSession
}
const persistedReducer = persistReducer(persistConfig, reducer)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOST_ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOST__({}) : compose
const enhancer = composeEnhancers(applyMiddleware(thunk))
const store = createStore(persistedReducer, enhancer)
export default () => {
const persistor = persistStore(store)
return { store, persistor }
}
// store/reducer.js
import * as types from './actionTypes'
const defaultState = {
langId: 1,
isLogin: false
}
const initState = JSON.parse(JSON.stringify(defaultState))
const reducer = (state = defaultState, action) => {
switch (action.type) {
case types.INIT_STORE:
return initState
case types.CHANGE_LANGUAGE:
return {
...state,
langId: action.langId
}
case types.SET_LOGIN:
return {
...state,
isLogin: action.isLogin
}
default:
return state
}
}
export default reducer
// store/actionTypes.js
export const INIT_STORE = 'INIT_STORE'
export const CHANGE_LANGUAGE = 'CHANGE_LANGUAGE'
export const SET_LOGIN = 'SET_LOGIN'
// store/actionCreators.js
import * as types from './actionTypes'
export const changeLanguage = (langId) => {
return dispath => {
dispath({
type: types.CHANGE_LANGUAGE,
langId
})
}
}
export const setLogin = (bol) => {
return dispath => {
dispath({
type: types.SET_LOGIN,
isLogin: bol
})
}
}
export const initStore = () => {
return dispath => {
dispath({
type: types.INIT_STORE
})
}
}
使用
// home.js
import {connect} from 'react-redux'
import {changeLanguage} from '../store/actionCreators'
class Home extends Component {
handleChange () {
this.props.changeLanguage(this.props.langId === 1 ? 2 : 1)
}
}
const mapStateToProps = state => ({
langId: state.langId
})
const mapDispathToProps = {
changeLanguage
}
export default connect(mapStateToProps, mapDispathToProps)(Home)
8. 使用路由
npm i react-router-dom
在index.js引入
// index.js
import Routers from './router'
...
<PersistGate persistor={persistor}>
// <App />
<Routers />
</PersistGate>
...
新建router文件夹,并新建index.js 和base-route.js两个文件
// router/index.js
import React, {Component} from 'react'
import {HashRouter as Router, Switch, Route, Redirect} from 'react-router-dom'
import Login from '../pages/login/login'
import Layout from '../pages/layout/index'
// class Routers extends Component {
// render
// }
function Routers () {
return (
<Router>
<Switch>
<Route component={Layout}></Route>
<Route exact path="/login" component={Login}></Route>
</Switch>
</Router>
)
}
export default Routers
// router/bser-route.js
import React from 'react'
import {HashRouter as Router, Switch, Route, Redirect} from 'react-router-dom'
import Error from '../pages/error/error'
import Home from '../pages/home'
import Classify from '../pages/classify'
import Car from '../pages/car'
import Mine from '../pages/mine'
function BaseRoute () {
return (
<Router>
<Switch>
<Route exact path="/" component={Home}></Route>
<Route exact path="/home" component={Home}></Route>
<Route exact path="/classify" component={Classify}></Route>
<Route exact path="/car" component={Car}></Route>
<Route exact path="/mine" component={Mine}></Route>
{/* 404 */}
<Route path="/error" component={Error}></Route>
<Redirect from="/*" to="/error" ></Redirect>
</Switch>
</Router>
)
}
export default BaseRoute
修改layout文件
// pages/layout/index.js
import React, {Component} from 'react'
import style from './index.module.scss'
import BaseRoute from '../../router/base-route'
class Layout extends Component {
constructor (props) {
super(props)
this.goOther = this.goOther.bind(this)
}
componentWillMount () {
// console.log('this.', this)
}
goOther (path) {
// 使用
this.props.history.push({
pathname: path
})
}
render () {
const nav = [
{name: '首页', path: '/'},
{name: '分类', path: '/classify'},
{name: '购物车', path: '/car'},
{name: '我的', path: '/mine'},
]
let pathLists = ['/', '/home', '/classify', '/car', '/mine']
let pathNow = this.props.location.pathname
console.log('style', style)
return (
<div>
<BaseRoute></BaseRoute>
{
pathLists.indexOf(pathNow) > -1 ? (
<ul className={`${style['footer']}`}>
{
nav.map(item => (
<li key={item.path} className={`${style['footer-item']} ${pathNow === item.path ? style['footer-active'] : ''}`} onClick={() => this.goOther(item.path)}>{item.name}</li>
))
}
</ul>
) : null
}
</div>
)
}
9. 添加antd-mobile
npm i antd-mobile babel-plugin-import
修改package.json文件
"babel": {
"presets": [
"react-app"
],
"plugins": [
["import", { "libraryName": "antd-mobile", "style": "css" }]
]
}
使用
import {Button, Toast} from 'antd-mobile'
...
componentDidMount () {
Toast.loading('loading', 0)
setTimeout(() => {
Toast.hide()
}, 3000)
}
...
<Button>添加购物车</Button>
10.使用axios
新建文件配置axios
//api/axios-set.js
import axios from 'axios'
const baseUrl = "https://api.github.com"
axios.defaults.baseURL = baseUrl
//设置全局允许跨域
//axios.defaults.withCredentials = true
axios.interceptors.request.use(config => {
return config
})
axios.interceptors.response.use(response => {
const code = response.status
if (code === 601) {
// 未登录
// message.error('请登录!')
return
}
if (code !== 200 && code !== 0) {
// return
}
return response
})
在入口文件引入
// index.js
import './api/axios-set'
使用
import axios from 'axios'
...
getLists = () => {
axios.get('/users').then(res => {
this.setState({
lists: res.data
})
}).catch(err => {
})
}
...