从0开始搭建react移动端

首先附上地址 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 => {
    })
}
...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值