乐趣区

使用createreactapp快速构建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 的版本才运行成功

退出移动版