使用create-react-app快速构建React项目

从学习的角度来说,react没有vue那么容易上手。万事开头难,然后中间难,结果难......下面是我一步步集成插件,搭建基本框架的过程。

1 使用create-react-app

通过官方的脚手架工具创建项目目录

npx create-react-app react-app
第一行的 npx 不是拼写错误,它是 npm 5.2+ 附带的 package 运行工具。

'安装脚手架'

稍等一会,安装完成后,使用npm或者yarn启动项目

npm start(yarn start)
项目启动后,默认的端口号是3000,可以通过localhost:3000来访问

改变一下原本的目录解构,调整成个人比较习惯或者舒适的
'目录结构'

2 基本配置

2.1 支持Css预处理器

create-react-app默认情况下是不暴露配置文件的,执行npm run eject暴露配置文件的操作是不可逆的。
个人喜欢使用react-app-rewired自定义配置(类似于vue-cli3的自定义配置文件)

react-app-rewired 1.x 配合 create-react-app 1.x
react-app-rewired 2.x 配合 create-react-app 2.x以上
react-app-rewired@^2.0.0+ 版本需要搭配 customize-cra 使用

2.1.1 支持Scss

create-react-app原本就自带sass-loader和sass相关的配置,你只需要安装node-sass就可以使用了

npm i node-sass -D
注意
安装node-sass的时候总是会各种不成功,主要因为在安装时会从 github.com 上下载一个 .node 文件
方法一就是直接使用cnpm安装一切包
方法二是使用代理
我比较喜欢使用npm i node-sass --sass_binary_site=https://npm.taobao.org/mirror...

2.1.2 支持Less

后续因为会集成antd,所以预处理器我推荐使用less比较统一,执行:

npm i react-app-rewired customize-cra -D

修改package.json

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
}

然后在根目录新建config-overrides.js并添加配置:

const { override, addLessLoader } = require('customize-cra')
const path = require('path')

module.exports = override(
  addLessLoader()
)

2.2 引入全局公共样式

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './common/style/reset.less'
import './common/style/base.less'
import './common/fonts/iconfont.css'

ReactDOM.render(<App />, document.getElementById('root'))

3 路由

执行安装命令:

npm i react-router-dom

修改App.jsx文件:

import React, { Component } from 'react'
import { Router } from 'react-router-dom'
import history from './history'
import RouteConfig from './routes'

export default class App extends Component {
  render() {
    return (
      <Router history={history}>
        <RouteConfig />
      </Router>
    )
  }
}

正常情况应该引入BrowserRouter或者HashRouter,我使用这种方案是为了实现非组件js跳转hack(封装http请求拦截的时候会用到)

// history.js
import { createBrowserHistory } from 'history';
const history = createBrowserHistory()

export default history

BaseLayout是基础布局组件,Suspense与lazy配合import()实现路由的按需加载(react版本16.6.0+才支持这种形式)

//RouteConfig routes的index.js
import React, { Suspense } from 'react'
import { Route, Switch } from 'react-router-dom'
import { Spin } from 'antd';
import BaseLayout from '../page/layout'
import Home from '../page/home'
import Login from '../page/login'
import NotFound from '../page/other/404'
import accounts from './accounts'
import loan from './loan'

const allRoutes = [
  ...accounts,
  ...loan
]

const Loading = () => {
  return (
    <div
      className="loading"
      style={{
        paddingTop: '50px',
        textAlign: 'center'
      }}
    >
      <Spin size="large" />
    </div>
  )
}

export default () => {
  return (
    <Switch>
      <Route path="/login" component={Login}></Route>
      <Route path="/" render={(props) => (
        <BaseLayout {...props}>
          <Suspense fallback={<Loading />}>
            <Switch>
              <Route exact path="/" component={Home} />
              {
                allRoutes.map((route, index) => <Route path={route.path} key={index} component={route.comp} />)
              }
              <Route path="*" component={NotFound} />
            </Switch>
          </Suspense>
        </BaseLayout>
      )}></Route>
    </Switch>
  )
}

4 Layout基础布局组件

引入antd并且按需引用,执行命令:

npm i antd
npm i babel-plugin-import -D

修改config-overrides.js文件:

const { override, fixBabelImports, addLessLoader } = require('customize-cra')
const path = require('path')

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
  addLessLoader()
)

Layout主文件

import React, { Component } from 'react'
import { Layout } from 'antd'
import SiderBar from './siderbar'
import MainView from './mainView'

const { Sider } = Layout

export default class BaseLayout extends Component {
  constructor() {
    super()
    this.state = {
      collapsed: false,
    }
  }
  onCollapse = (collapsed) => {
    this.setState({
      collapsed,
    })
  }
  render() {
    return (
      <Layout className="layout">
        <Sider
          width="230px"
          collapsible
          className="layout-sider"
          style={{
            position: 'fixed',
            left: '0',
            top: '0',
            bottom: '0',
            paddingTop: '10px',
          }}
          onCollapse={this.onCollapse}
        >
          <div
            className="iconfont icon-logo"
            style={{
              lineHeight: 1,
              fontSize: this.state.collapsed ? '50px' : '100px',
              color: '#fff',
              textAlign: 'center',
            }}
          ></div>
          <p
            className="layout-sider__desc"
            style={{
              paddingTop: '10px',
              whiteSpace: 'nowrap',
              color: '#fff',
              textAlign: 'center',
            }}
          >
            {this.state.collapsed ? 'jack ma' : '欢迎您,jack ma'}
          </p>
          <SiderBar pathname={this.props.location.pathname}></SiderBar>
        </Sider>
        <MainView collapsed={this.state.collapsed}>{this.props.children}</MainView>
      </Layout>
    )
  }
}

5 封装http请求

在vue中我一般把封装的http请求挂载在this上,在react中就是用到什么引用什么就完事了

5.1 axios实例

import axios from 'axios'
import defaultConfig from './config'
import { notification } from 'antd'
import history from '../history'

const axiosInstance = axios.create(defaultConfig)

axiosInstance.interceptors.request.use(
  config => {
    return config
  },
  error => {
    console.log(error)
    return Promise.reject(error)
  }
)

axiosInstance.interceptors.response.use(
  response => {
    let data = response.data

   ...........................
    
    ......拦截器里的逻辑判断
    
    ..........................
      notification.error({
        message: '错误',
        description: data.message
      })
    }
    return data
  },
  error => {
    if (error.response) {
      switch (error.response.status) {
        case 500:
          notification.error({
            message: '错误',
            description: '接口服务错误'
          })
          break
        case 404:
          notification.error({
            message: '错误',
            description: '接口不存在'
          })
          break
        case 403:
          notification.error({
            message: '错误',
            description: '用户信息过期'
          })
          history.push('/login')
          break
        case 401:
          notification.error({
            message: '错误',
            description: '登录已过期'
          })
          break
        default:
          notification.error({
            message: '错误',
            description: '接口无法连接'
          })
          break
      }
    }
    return Promise.reject(error)
  }
)

export default axiosInstance

5.2 封装的请求函数

import axiosInstance from './axios'

export const GET = (url, params) => {
  return axiosInstance({
    method: 'get',
    url,
    params
  }).then(res => {
    return Promise.resolve(res)
  }).catch(err => {
    return Promise.reject(err)
  })
}

export const POST = (url, data) => {
  return axiosInstance({
    method: 'post',
    url,
    data
  }).then(res => {
    return Promise.resolve(res)
  }).catch(err => {
    return Promise.reject(err)
  })
}

5.3 api管理

import { GET, POST } from '@/http'

export const doLogin = (data = {}) => {
  return POST('后端的url', data)
}

export const getMenuData = (params = {}) => {
  return GET('后端的url', params)
}

5.4 页面中使用

..... 省略代码
import { getMenuData } from '@/api'

export default class Siderbar extends Component {
  constructor() {
    super()
    this.state = {
      menuTree: []
    }
  }
  componentDidMount() {
    this.getMenu()
  }
  getMenu = async () => {
    const menus = await getMenuData()
    // 递归循环出菜单
    const menuTree = this.renderMenu(menus)
    this.setState({
      menuTree,
    })
  }
}

最后

1 配置跨域

create-react-app低于2.0的时候可以在package.json中:

"proxy":{
  "/manage":{
    "target":"http://test.xxx.com",
    "changeOrigin": true
  }
}

create-react-app的版本高于 2.0 版本的时候在 package.json 只能配置 string 类型,所以执行命令安装:

npm i http-proxy-middleware -D

在src目录新建setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware')

module.exports = function (app) {
  app.use('/api', createProxyMiddleware({
    target: 'https://test.xxx.com',
    changeOrigin: true,
    secure: false,
    pathRewrite: {
      '/api': ''
    }
  }));
};

2 其他配置

配置alias,修改config-overrides.js

const { override, fixBabelImports, addWebpackAlias, addLessLoader } = require('customize-cra')
const path = require('path')

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
  addWebpackAlias({
    ['@']: path.resolve(__dirname, './src')
  }),
  addLessLoader()
);

vue可以使用scoped来避免样式全局污染,在react中一般在组件里直接使用对象的方式或者在类名命名的时候加以区分。这边我使用修改config-overrides.js中addLessLoader的配置来实现CSS module

const { override, fixBabelImports, addWebpackAlias, addLessLoader } = require('customize-cra')
const path = require('path')

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
  addWebpackAlias({
    ['@']: path.resolve(__dirname, './src')
  }),
  addLessLoader({
    localIdentName: '[local]--[hash:base64:5]'
  })
);

这时候你的less命名需要xx.module.less
而且如果你安装的less-loader版本大于6.0,就会报一个错误


在网上找了许多方法没有结局,最终将less-loader降到5.x的版本才运行成功

阅读 368

推荐阅读