REACT实战项目从0到1搭建(仅供参考)

时间:2024-10-01 07:18:36

目录

一、环境搭建

1、安装

2、安装webpack

3、配置淘宝镜像(看个人需求)

4、安装create-react-app

5、创建项目

6、本地服务启动

二、项目框架结构

三、常用的集成和配置

1、React Router(路由配置管理)

2、redux(数据管理)

2.1 在函数组件里使用

2.2 在Class组件里使用

3、axios

4、UI组件 

5、craco

6、多环境打包运行配置

1.1、内置环境

1.2、dotenv

7、使用代理proxy(http-proxy-middleware)

四、开箱即用的前端开发框架

1、UMI

2、Ant Design Pro

学习参考文档


一、环境搭建

1、安装

官网地址:

2、安装webpack

npm install -g webpack

3、配置淘宝镜像(看个人需求)

如果在本地一直装不上npm的包,可以考虑用国内的淘宝镜像,使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm

  1. npm install -g cnpm --registry=
  2. npm config set registry

4、安装create-react-app

npm install -g create-react-app

5、创建项目

  1. create-react-app my-project // 创建名为my-project的项目
  2. cd my-project // 进入项目文件夹

6、本地服务启动

npm run start // 启动本地server用于开发

二、项目框架结构

  1. |- node_modules // 项目依赖包
  2. |- public // 一般用于存放静态文件,打包时会被直接复制到输出目录(./buidle)
  3. |- src // 项目源代码
  4. | |- assets // 静态资源文件夹,用于存放静态资源,打包时会经过 webpack 处理
  5. | |- components // 组件文件夹,存放 React 组件,一般是该项目公用的无状态组件
  6. | |- MLayout
  7. | |-
  8. | |-
  9. | |- containers // 页面视图文件夹
  10. | |- Index
  11. | |-
  12. | |-
  13. | |- redux // redux文件夹
  14. | |- actions
  15. | |-
  16. | |- reducers
  17. | |-
  18. | |- store
  19. | |-
  20. | |-
  21. | |- router // 路由配置文件夹
  22. | |- // 路由文件配置
  23. | |- service // 服务请求文件夹
  24. | |- // 请求接口文件
  25. | |- // 请求配置文件
  26. | |- // 入口文件
  27. | |-
  28. | |- //注册路由与服务
  29. | |-
  30. | |- serviceWorker //开发配置
  31. |- .env // 环境配置文件
  32. |- . // dev环境配置文件
  33. |- . // test环境配置文件
  34. |- . // prd环境配置文件
  35. |- // craco配置文件
  36. |- // 包管理代码
  37. |- .gitignore // Git忽略文件

三、常用的集成和配置

1、React Router(路由配置管理)

React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。

1.1、安装react-router

npm install react-router@4.3.1 --save
  1. Router下面只能包含一个盒子标签,类似这里的div。 
  2. Link代表一个链接,在html界面中会解析成a标签。作为一个链接,必须有一个to属性,代表链接地址。这个链接地址是一个相对路径。 
  3. Route,有一个path属性和一个组件属性(可以是component、render等等)。

 1.2、基本使用:

  1. render () {
  2. return (
  3. <Router>
  4. <div>
  5. <ul>
  6. <li><Link to="/index">首页</Link></li>
  7. <li><Link to="/other">其他页</Link></li>
  8. </ul>
  9. <Route path="/index" component={Index}/>
  10. <Route path="/other" component={Other}/>
  11. </div>
  12. </Router>
  13. )
  14. }

1.3 路由嵌套使用

  1. const Test = () => (
  2. <div>
  3. <Switch>
  4. <Route
  5. path="/"
  6. render={props => (
  7. <App>
  8. <Switch>
  9. <Route path="/" exact component={Index} />
  10. <Route path="/index" component={Index} />
  11. <Route path="/other" component={Other} />
  12. <Route path="/message/:id/:TestId" component={Message} />
  13. {/*路由不正确时,默认跳回home页面*/}
  14. <Route render={() => <Redirect to="/" />} />
  15. </Switch>
  16. </App>
  17. )}
  18. />
  19. </Switch>
  20. </div>
  21. );
  22. export default Test;

1.4、把路由路径写在一个配置文件里,示例代码如下

  1. import Login from '../containers/Login/';
  2. import Index from '../containers/Index/';
  3. import Home from '../containers/';
  4. import New from '../containers/';
  5. import NewOne from '../containers/';
  6. let routes = [
  7. {
  8. path: "/",
  9. component: Home,
  10. exact: true
  11. },
  12. {
  13. path: "/login",
  14. component: Login,
  15. exact: true
  16. },
  17. {
  18. path: "/index",
  19. component: Index,
  20. exact: true
  21. },
  22. {
  23. path: "/news",
  24. component: New,
  25. routes: [ // 嵌套路由
  26. {
  27. path: "/news/newsone",
  28. component: NewOne,
  29. exact: true
  30. }
  31. ]
  32. }
  33. ];
  34. export default routes;

在里导入使用 

  1. import './';
  2. import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
  3. import routes from './router/router';
  4. import Home from './containers/';
  5. // 默认语言为 en-US,如果你需要设置其他语言,推荐在入口文件全局设置 locale
  6. import moment from 'moment';
  7. import 'moment/locale/zh-cn';
  8. import locale from 'antd/lib/locale/zh_CN';
  9. import { ConfigProvider } from 'antd';
  10. function App() {
  11. return (
  12. <ConfigProvider locale={locale}>
  13. <div className="App">
  14. <Router>
  15. <Switch>
  16. {
  17. ((route, key) => {
  18. (route)
  19. if () {
  20. return <Route key={key} exact path={} component={}/>
  21. } else {
  22. return <Route key={key} path={}
  23. render={props => < {...props} routes={} />
  24. }/>
  25. }
  26. })
  27. }
  28. <Route component={Home}/>
  29. </Switch>
  30. </Router>
  31. </div>
  32. </ConfigProvider>
  33. );
  34. }
  35. export default App;

(父页面)

  1. import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
  2. function New(props) {
  3. return <div>
  4. new
  5. {
  6. ((route, key) => {
  7. ()
  8. return <Route key={key} exact path={} component={}/>
  9. })
  10. }
  11. </div>
  12. }
  13. export default New

(嵌套的子页面)

  1. function NewOne() {
  2. return <div>new one</div>
  3. }
  4. export default NewOne

2、redux(数据管理)

React是单向数据流,所以有些情况下单单依赖React自身无法实现组件之间的通信,故此需要redux

需要安装Redux相关依赖,由于不同版本之间可能存在兼容性问题,所以安装的时候最好制定版本,安装好之后,就可以在项目中引入redux了

  1. npm install redux@3.7.2 --save
  2. npm install redux-thunk@2.1.0 --save
  3. npm install react-redux@5.0.6 --save

2.1 在函数组件里使用

(声明变量默认值)

  1. // state的默认值统一放置在文件
  2. //
  3. // 声明默认值
  4. // 这里我们列举两个示例
  5. // 同步数据:pageTitle
  6. // 异步数据:infoList(将来用异步接口获取)
  7. export default {
  8. pageTitle: '首页',
  9. infoList: []
  10. }

(定义触发事件)

  1. //
  2. // action也是函数
  3. export function increaseAction (data) {
  4. return (dispatch, state) => {
  5. dispatch({ type: 'increase', data: data })
  6. }
  7. }
  8. export function setInfoList (data) {
  9. return (dispatch, getState) => {
  10. // // 使用fetch实现异步请求
  11. // ('/api/getInfoList', {
  12. // method: 'GET',
  13. // headers: {
  14. // 'Content-Type': 'application/json'
  15. // }
  16. // }).then(res => {
  17. // return ()
  18. // }).then(data => {
  19. // let { code, data } = data
  20. // if (code === 0) {
  21. // dispatch({ type: 'SET_INFO_LIST', data: data })
  22. // }
  23. // })
  24. }
  25. }

 (获取和处理数据)

  1. //
  2. // 工具函数,用于组织多个reducer,并返回reducer集合
  3. import { combineReducers } from 'redux'
  4. // 默认值
  5. import defaultState from '../'
  6. // 一个reducer就是一个函数
  7. function pageTitle (state = defaultState.pageTitle, action) {
  8. // 不同的action有不同的处理逻辑
  9. switch (action.type) {
  10. case 'SET_PAGE_TITLE':
  11. return action.data
  12. default:
  13. return state
  14. }
  15. }
  16. function infoList (state = defaultState.infoList, action) {
  17. switch (action.type) {
  18. case 'SET_INFO_LIST':
  19. return action.data
  20. default:
  21. return state
  22. }
  23. }
  24. // 导出所有reducer
  25. export default combineReducers({
  26. pageTitle,
  27. infoList
  28. })

 (创建store实例)

  1. // applyMiddleware: redux通过该函数来使用中间件
  2. // createStore: 用于创建store实例
  3. // 中间件,作用:如果不使用该中间件,当我们dispatch一个action时,
  4. // 需要给dispatch函数传入action对象;但如果我们使用了这个中间件,那么就可
  5. // 以传入一个函数,这个函数接收两个参数:dispatch和getState。这个dispatch可
  6. // 以在将来的异步请求完成后使用,对于异步action很有用
  7. import { applyMiddleware, createStore } from 'redux'
  8. import thunk from 'redux-thunk'
  9. // 引入reducer
  10. import reducers from '../reducers/'
  11. // 创建store实例
  12. let store = createStore(
  13. reducers,
  14. applyMiddleware(thunk)
  15. )
  16. export default store

在根文件里使用,向目标元素渲染容器组件App

这里的组件Provider是一个react-redux中特殊的组件

1. Provider中有且只有一个子组件(这里就是App容器组件,不一定是容器组件,根据自己的业务需求自己操作)

2. 使用Provider组件的好处是,只需要给Provider组件设置属性,那么其子组件和其子组件中的子组件都可以直接使用其对应的属性

3. 避免了组件嵌套之后一个一个传递的复杂操作

  1. import React, { useEffect } from 'react';
  2. import ReactDOM from 'react-dom';
  3. import './';
  4. import App from './App';
  5. import reportWebVitals from './reportWebVitals';
  6. // Provider是react-redux两个核心工具之一,作用:将store传递到每个项目中的组件中
  7. // 第二个工具是connect,稍后会作介绍
  8. import { Provider } from 'react-redux'
  9. // 引入创建好的store实例
  10. import store from './redux/store/'
  11. ReactDOM.render(
  12. <div>
  13. {/* 将store作为prop传入,即可使应用中的所有组件使用store */}
  14. <Provider store = {store}>
  15. <App />
  16. </Provider>
  17. </div>,
  18. document.getElementById('root')
  19. );
  20. // If you want to start measuring performance in your app, pass a function
  21. // to log results (for example: reportWebVitals())
  22. // or send to an analytics endpoint. Learn more: /CRA-vitals
  23. reportWebVitals();

 在UI页面里使用

  1. import { Layout, Button, Form, Input } from 'antd';
  2. import style from './';
  3. import { useRef } from 'react'
  4. // connect方法的作用:将额外的props传递给组件,并返回新的组件,组件在该过程中不会受到影响
  5. import { connect } from 'react-redux';
  6. // 引入action
  7. import { increaseAction, setInfoList } from '../../redux/actions/'
  8. const { Header, Footer, Content } = Layout;
  9. function Login(props) {
  10. const formRef = useRef(null);
  11. const onReset = () => {
  12. formRef.current.resetFields();
  13. };
  14. const onFinish = (values) => {
  15. console.log('Success:', values);
  16. props.history.push('/index')
  17. };
  18. const onFinishFailed = (errorInfo) => {
  19. console.log('Failed:', errorInfo);
  20. };
  21. return <Layout style={{height: '100vh'}}>
  22. ...省略代码
  23. </Layout>
  24. }
  25. // mapStateToProps:将state映射到组件的props中
  26. const mapStateToProps = (state) => {
  27. return {
  28. pageTitle: state.pageTitle,
  29. infoList: state.infoList
  30. }
  31. }
  32. // mapDispatchToProps:将dispatch映射到组件的props中
  33. const mapDispatchToProps = (dispatch, ownProps) => {
  34. return {
  35. increaseAction (data) {
  36. // 如果不懂这里的逻辑可查看前面对redux-thunk的介绍
  37. dispatch(increaseAction(data))
  38. // 执行increaseAction会返回一个函数
  39. // 这正是redux-thunk的所用之处:异步action
  40. // 上行代码相当于
  41. /*dispatch((dispatch, getState) => {
  42. dispatch({ type: 'SET_PAGE_TITLE', data: data })
  43. )*/
  44. },
  45. setInfoList (data) {
  46. dispatch(setInfoList(data))
  47. }
  48. }
  49. }
  50. export default connect(mapStateToProps, mapDispatchToProps)(Login)

2.2 在Class组件里使用

安装react-redux依赖包

  1. npm i react-redux --save
  2. npm i @types/react-redux --save-dev

src/

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import { Provider } from 'react-redux' // react-redux 利用上下文context,提供的数据组件Provider
  4. import 'antd/dist/'
  5. import './'
  6. import App from './App'
  7. import 'i18n'
  8. import store from 'redux/store'
  9. ReactDOM.render(
  10. <>
  11. {/* 使用Provider, 并加载 数据仓库 store, 就可以在全局范围内使用store */}
  12. <Provider store={store}>
  13. <App />
  14. </Provider>
  15. </>,
  16. document.getElementById('root')
  17. )

Header组件中使用

  1. import { Component } from 'react'
  2. import { withRouter, RouteComponentProps } from 'react-router-dom'
  3. import { MenuInfo } from 'rc-menu/lib/interface'
  4. import { nanoid } from 'nanoid'
  5. // 使用react-redux
  6. import { Dispatch } from 'redux'
  7. import { connect } from 'react-redux'
  8. import store, { RootState } from 'redux/store'
  9. import { addLanguageActionCreator, changeLanguageActionCreator } from 'redux/language/actionCreators'
  10. /* connect 就是一个高阶hoc,只不过它的命名没有使用withxxxx来表示,class 类组件 使用 withxxx 高阶hoc组件
  11. 使用了connect,就相当于, 即组件订阅了store中的数据
  12. */
  13. /* mapStateToProps: 处理数据的流入。返回一个对象
  14. 使用connect函数,传入mapStateToProps,完成store数据与组件的props绑定
  15. */
  16. // 高阶hoc包裹的组件,可以获取到一些额外的props或state属性,connect函数传入参数 mapStateToProps, 组件的props会被注入一些额外的props属性
  17. const mapStateToProps = (state: RootState) => {
  18. return {
  19. lng: state.lng,
  20. languageList: state.languageList
  21. }
  22. }
  23. /* mapDispatchToProps: 处理数据的流出。返回一个对象,对象中的每一个字段都是一个dispatch处理函数
  24. 将dispatch绑定到props中
  25. */
  26. const mapDispatchToProps = (dispatch: Dispatch) => {
  27. return {
  28. addLanguageDispatch: (language: { code: string, language: string }) => {
  29. dispatch(addLanguageActionCreator(language))
  30. }
  31. changeLanguageDispatch: (lng: 'en'|'zh') => {
  32. dispatch(changeLanguageActionCreator(lng))
  33. }
  34. }
  35. }
  36. class HeaderComponent extends Component<RouteComponentProps & ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>>{
  37. render(){
  38. const { history, lng, languageList, addLanguageDispatch, changeLanguageDispatch } = this.props
  39. /* meun 的点击事件 */
  40. const oprateLanguage = ( e: MenuInfo ) => {
  41. if(e.key !== 'new'){
  42. changeLanguageDispatch(e.key)
  43. }else{
  44. addLanguageDispatch({code: `lng${nanoid()}`, language: '新语种'})
  45. }
  46. }
  47. const menu = (
  48. <Menu>
  49. < key='new' onClick={oprateLanguage}>
  50. 添加新语言
  51. </>
  52. {(language => (
  53. < key={} onClick={oprateLanguage}>
  54. {}
  55. </>
  56. ))}
  57. </Menu>
  58. )
  59. return (
  60. <div>
  61. < className='mr40'>让旅游更幸福</>
  62. <Dropdown overlay={menu}>
  63. <Button>
  64. {(language => === lng)?.language}
  65. <GlobalOutlined />
  66. </Button>
  67. </Dropdown>
  68. <>
  69. <Button onClick={() => ('/signIn')}>登录</Button>
  70. <Button onClick={() => ('/register')}>注册</Button>
  71. </>
  72. </div>
  73. )
  74. }
  75. }
  76. export const Header = connect(mapStateToProps, mapDispatchToProps)(withRouter(HeaderComponent))

store数据封装

  • 新建目录:src/reduxsrc/redux/language
  • 新建文件:src/redux/src/redux/language/src/redux/language/
  1. mkdir src/redux src/redux/language
  2. touch src/redux/language/
  3. touch src/redux/language/

  1. import { createStore } from 'redux'
  2. import languageReducer form './language/'
  3. const store = createStore(languageReducer)
  4. // 使用ts的条件类型 ReturnType<T>,T:函数类型。 获取函数返回值的类型
  5. export type RootState = ReturnType<typeof store.getState>
  6. export default store

工厂模式创建action- 

  1. /* 用常量定义,减少代码敲错 */
  2. export const ADD_LANGUAGE = 'language/add'
  3. export const CHANGE_LANGUAGE = 'language/change'
  4. /* action的类型申明 */
  5. const AddActionProps = {
  6. type: typeof ADD_LANGUAGE,
  7. payload: { code: string, language: string }
  8. }
  9. const ChangeActionProps = {
  10. type: typeof CHANGE_LANGUAGE,
  11. payload: 'zh' | 'en'
  12. }
  13. export type LanguageActionProps = AddActionProps | ChangeActionProps
  14. /* 用工厂模式创建action */
  15. export const addLanguageActionCreator = (language: {code: string, language: string}):ADD_LANGUAGE => {
  16. return {
  17. type: ADD_LANGUAGE,
  18. payload: language
  19. }
  20. }
  21. export const changeLanguageActionCreator = (lng: 'zh' | 'en'):CHANGE_LANGUAGE => {
  22. return {
  23. type: CHANGE_LANGUAGE,
  24. payload: lng
  25. }
  26. }

  1. import { ADD_LANGUAGE, CHANGE_LANGUAGE, LanguageActionProps } from './actions'
  2. export interface LanguageState {
  3. lng: 'zh' | 'en',
  4. languageList: {code: string, language: string}[]
  5. }
  6. const defaultStoreState: LanguageState = {
  7. lng: 'zh',
  8. languageList: [{ code: 'zh', language: '中文'}, { code: 'en', language: 'English'}]
  9. }
  10. export default (state = defaultStoreState, action:LanguageActionProps) => {
  11. switch (action.type) {
  12. case CHANGE_LANGUAGE:
  13. return { ...state, lng: action.payload }
  14. case ADD_LANGUAGE:
  15. return { ...state, languageList: [...state.languageList, action.payload] }
  16. default:
  17. return state
  18. }
  19. }

3、axios

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 中。

安装

npm install axios

示例代码

request.js

  1. import axios from 'axios'
  2. const BASE_URL = process.env.BASE_URL;
  3. const TIMEOUT = 5000;
  4. const instance = axios.create({ // 创建axios实例,在这里可以设置请求的默认配置
  5. timeout: TIMEOUT, // 设置超时时间
  6. baseURL: BASE_URL // 根据自己配置的反向代理去设置不同环境的baeUrl
  7. })
  8. // 文档中的统一设置post请求头。下面会说到post请求的几种'Content-Type'
  9. instance.defaults.headers.post['Content-Type'] = 'application/json'
  10. let httpCode = { //这里我简单列出一些常见的http状态码信息,可以自己去调整配置
  11. 400: '请求参数错误',
  12. 401: '权限不足, 请重新登录',
  13. 403: '服务器拒绝本次访问',
  14. 404: '请求资源未找到',
  15. 500: '内部服务器错误',
  16. 501: '服务器不支持该请求中使用的方法',
  17. 502: '网关错误',
  18. 504: '网关超时'
  19. }
  20. /** 添加请求拦截器 **/
  21. instance.interceptors.request.use(config => {
  22. config.headers['token'] = sessionStorage.getItem('token') || ''
  23. // hide = ({content: 'Loading...', duration: 0});
  24. // 在这里:可以根据业务需求可以在发送请求之前做些什么:例如我这个是导出文件的接口,因为返回的是二进制流,所以需要设置请求响应类型为blob,就可以在此处设置。
  25. if (config.url.includes('pur/contract/export')) {
  26. config.headers['responseType'] = 'blob'
  27. }
  28. // 我这里是文件上传,发送的是二进制流,所以需要设置请求头的'Content-Type'
  29. if (config.url.includes('pur/contract/upload')) {
  30. config.headers['Content-Type'] = 'multipart/form-data'
  31. }
  32. return config
  33. }, error => {
  34. // 对请求错误做些什么
  35. return Promise.reject(error)
  36. })
  37. /** 添加响应拦截器 **/
  38. instance.interceptors.response.use(response => {
  39. // hide()
  40. if (response.statusText === 'ok') { // 响应结果里的statusText: ok是我与后台的约定,大家可以根据实际情况去做对应的判断
  41. return Promise.resolve(response.data)
  42. } else {
  43. message.error('响应超时')
  44. return Promise.reject(response.data.message)
  45. }
  46. }, error => {
  47. // hide()
  48. if (error.response) {
  49. // 根据请求失败的http状态码去给用户相应的提示
  50. // let tips = in httpCode ? httpCode[] :
  51. // (tips)
  52. if (error.response.status === 401) { // token或者登陆失效情况下跳转到登录页面,根据实际情况,在这里可以根据不同的响应错误结果,做对应的事。这里我以401判断为例
  53. // 针对框架跳转到登陆页面
  54. this.props.history.push(LOGIN);
  55. }
  56. return Promise.reject(error)
  57. } else {
  58. message.error('请求超时, 请刷新重试')
  59. return Promise.reject('请求超时, 请刷新重试')
  60. }
  61. })
  62. /* 统一封装get请求 */
  63. export const get = (url, params, config = {}) => {
  64. return new Promise((resolve, reject) => {
  65. instance({
  66. method: 'get',
  67. url,
  68. params,
  69. ...config
  70. }).then(response => {
  71. resolve(response)
  72. }).catch(error => {
  73. reject(error)
  74. })
  75. })
  76. }
  77. /* 统一封装post请求 */
  78. export const post = (url, data, config = {}) => {
  79. return new Promise((resolve, reject) => {
  80. instance({
  81. method: 'post',
  82. url,
  83. data,
  84. ...config
  85. }).then(response => {
  86. resolve(response)
  87. }).catch(error => {
  88. reject(error)
  89. })
  90. })
  91. }

  1. import {get, post} from './request'
  2. export const requestIndex = () => {
  3. return get('api/index').then((res) => {
  4. // return
  5. console.log(res)
  6. })
  7. }

4、UI组件 

基于React的UI组件在这里推荐两个,一个是蚂蚁金服的Ant Design;另外一个是Material-UI

两个都很不错,这里使用的是Ant Design。

安装

npm install antd --save

 修改 src/,引入 antd 的组件

  1. import React from 'react';
  2. import { Button } from 'antd';
  3. import './';
  4. const App = () => (
  5. <div className="App">
  6. <Button type="primary">Button</Button>
  7. </div>
  8. );
  9. export default App;

修改 src/,在文件顶部引入 antd/dist/

@import '~antd/dist/';

好了,现在你应该能看到页面上已经有了 antd 的蓝色按钮组件,接下来就可以继续选用其他组件开发应用了。其他开发流程你可以参考 create-react-app 的官方文档

高级配置

这个例子在实际开发中还有一些优化的空间,比如无法进行主题配置。

此时我们需要对 create-react-app 的默认配置进行自定义,这里我们使用 craco (一个对 create-react-app 进行自定义配置的社区解决方案)。

现在我们安装 craco 并修改  里的 scripts 属性。

npm install @craco/craco
  1. /* */
  2. "scripts": {
  3. - "start": "react-scripts start",
  4. - "build": "react-scripts build",
  5. - "test": "react-scripts test",
  6. + "start": "craco start",
  7. + "build": "craco build",
  8. + "test": "craco test",
  9. }

然后在项目根目录创建一个  用于修改默认配置。

  1. /* */
  2. module.exports = {
  3. // ...
  4. };
'
运行

 自定义主题

按照 配置主题 的要求,自定义主题需要用到类似 less-loader 提供的 less 变量覆盖功能。我们可以引入 craco-less 来帮助加载 less 样式和修改变量。

首先把 src/ 文件修改为 src/,然后修改样式引用为 less 文件。

  1. /* src/ */
  2. - import './';
  3. + import './';
  1. /* src/ */
  2. - @import '~antd/dist/';
  3. + @import '~antd/dist/';

 然后安装 craco-less 并修改  文件如下

npm install craco-less
  1. const CracoLessPlugin = require('craco-less');
  2. module.exports = {
  3. plugins: [
  4. {
  5. plugin: CracoLessPlugin,
  6. options: {
  7. lessLoaderOptions: {
  8. lessOptions: {
  9. modifyVars: { '@primary-color': '#1DA57A' },
  10. javascriptEnabled: true,
  11. },
  12. },
  13. },
  14. },
  15. ],
  16. };

这里利用了 less-loader 的 modifyVars 来进行主题配置,变量和其他配置方式可以参考 配置主题 文档。修改后重启 npm start,如果看到一个绿色的按钮就说明配置成功了。

antd 内建了深色主题和紧凑主题,你可以参照 使用暗色主题和紧凑主题 进行接入。

eject

你也可以使用 create-react-app 提供的 yarn run eject 命令将所有内建的配置暴露出来。不过这种配置方式需要你自行探索,不在本文讨论范围内。

5、craco

1、安装

  1. $ yarn add @craco/craco
  2. # OR
  3. $ npm install @craco/craco --save

2、在项目根目录新建文件

3、修改 里的启动配置

  1. /* */
  2. "scripts": {
  3. - "start": "react-scripts start",
  4. + "start": "craco start",
  5. - "build": "react-scripts build",
  6. + "build": "craco build",
  7. - "test": "react-scripts test",
  8. + "test": "craco test",
  9. }

通过上面3步已经可以扩展webpack配置了,接下来就根据需求去配置即可。

示例代码:

  1. /* */
  2. /**
  3. * TODO: 区分环境 —— NODE_ENV
  4. * - whenDev ☞ .NODE_ENV === 'development'
  5. * - whenTest ☞ .NODE_ENV === 'test'
  6. * - whenProd ☞ .NODE_ENV === 'production'
  7. */
  8. const CracoLessPlugin = require('craco-less');
  9. const CompressionPlugin = require("compression-webpack-plugin");
  10. const path = require('path')
  11. const pathResolve = pathUrl => path.join(__dirname, pathUrl)
  12. console.log(process.env)
  13. const dev = process.env.REACT_APP_ENV === 'development'
  14. const test = process.env.REACT_APP_ENV === 'test'
  15. const prod = process.env.REACT_APP_ENV === 'production'
  16. const outputDirName = dev ? 'dev-dist' : (test ? 'test-dist' : 'dist')
  17. module.exports = {
  18. webpack: {
  19. // 别名配置
  20. alias: {
  21. '@': pathResolve('src'),
  22. },
  23. /**
  24. * 重写 webpack 任意配置
  25. * - configure 能够重写 webpack 相关的所有配置,但是,仍然推荐你优先阅读 craco 提供的快捷配置,把解决不了的配置放到 configure 里解决;
  26. * - 这里选择配置为函数,与直接定义 configure 对象方式互斥;
  27. */
  28. configure: (webpackConfig, {
  29. env, paths
  30. }) => {
  31. // ='public'
  32. paths.appBuild = outputDirName // 配合输出打包修改文件目录
  33. // webpackConfig中可以解构出你想要的参数比如mode、devtool、entry等等,更多信息请查看文件
  34. /**
  35. * 修改 output
  36. */
  37. webpackConfig.output = {
  38. ...webpackConfig.output,
  39. path: path.resolve(__dirname, outputDirName), // 修改输出文件目录
  40. publicPath: process.env.REACT_APP_PUBLICPATH // 设置的是部署应用包的基本 URL,不是项目打包出来的文件存放的位置
  41. }
  42. // 返回重写后的新配置
  43. return webpackConfig
  44. },
  45. plugins: [
  46. // compression-webpack-plugin 因为版本问题,将 asset 改为了 filename
  47. new CompressionPlugin({
  48. filename: '[path].gz[query]', // 目标资源名称。[file] 会被替换成原资源。[path] 会被替换成原资源路径,[query] 替换成原查询字符串
  49. algorithm: 'gzip', // 算法
  50. test: new RegExp('\\.(js|css)$'), // 压缩 js 与 css
  51. threshold: 10240, // 只处理比这个值大的资源。按字节计算
  52. minRatio: 0.8 // 只有压缩率比这个值小的资源才会被处理
  53. }),
  54. ]
  55. },
  56. plugins: [
  57. {
  58. plugin: CracoLessPlugin,
  59. options: {
  60. lessLoaderOptions: {
  61. lessOptions: {
  62. modifyVars: {
  63. // '@primary-color': '#1DA57A',
  64. '@heading-color': '#1890ff' // 标题色
  65. },
  66. javascriptEnabled: true,
  67. },
  68. },
  69. },
  70. }
  71. ],
  72. // devServer: {
  73. // port: 9000,
  74. // proxy: {
  75. // '/api': {
  76. // target: '/',
  77. // changeOrigin: true,
  78. // secure: false,
  79. // xfwd: false,
  80. // }
  81. // }
  82. // }
  83. };

6、多环境打包运行配置

1.1、内置环境

有一个名为 NODE_ENV 的特殊内置环境变量。你可以从 .NODE_ENV 中读取它。

当你运行 npm start 时,它总是等于 'development' 

当你运行 npm test 它总是等于 'test' 

当你运行 npm run build 来生成一个生产 bundle(包) 时,它总是等于 'production' 

你无法手动覆盖NODE_ENV 这可以防止开发人员意外地将慢速开发构建部署到生产环境中。

在 .env 中添加开发环境变量:

  1. REACT_APP_BASE_URL=123
  2. REACT_APP_ENV=development

注意:你必须以 REACT_APP_ 开头创建自定义环境变量。除了 NODE_ENV 之外的任何其他变量都将被忽略,以避免意外地在可能具有相同名称的计算机上公开私钥。更改任何环境变量都需要重新启动正在运行的开发服务器。

其实查看 create-react-app 的官方文档可以发现,create-react-app 默认是支持多个环境配置文件的:

  • .env:默认。
  • .:本地覆盖。除 test 之外的所有环境都加载此文件。
  • ., ., .:设置特定环境。
  • ., ., .:设置特定环境的本地覆盖。

左侧的文件比右侧的文件具有更高的优先级:

  • npm start: ., ., ., .env
  • npm run build: ., ., ., .env
  • npm test: ., ., .env (注意没有 . )

1.2、dotenv

我们可以使用 dotenv 来做环境变量的管理(dotenv 可将环境变量从 .env 文件加载到 中)

安装dotenv-cli:

npm install dotenv-cli -g

在 里修改命令

  1. "scripts": {
  2. "dev": "GENERATE_SOURCEMAP=false cross-env PORT=5000 craco start",
  3. "test": "craco test",
  4. "prd": "dotenv -e . craco start",
  5. "build": "craco build",
  6. "build:dev": "dotenv -e . craco build",
  7. "build:test": "dotenv -e . craco build",
  8. "start-old": "cross-env PORT=5000 react-scripts start",
  9. "build-old": "react-scripts build",
  10. "test-old": "react-scripts test",
  11. "eject": "react-scripts eject"
  12. },

7、使用代理proxy(http-proxy-middleware)

安装

npm install http-proxy-middleware

建立 src/ 文件,可配置多个代理

  1. const proxy = require("http-proxy-middleware");
  2. module.exports = function(app) {
  3. app.use(
  4. proxy("/base/**", {
  5. target: "http://45.32.15.21:8090/",
  6. changeOrigin: true
  7. })
  8. );
  9. app.use(
  10. proxy("/fans/**", {
  11. target: "/mock/5c0f31837214cf627b8d43f0/",
  12. changeOrigin: true
  13. })
  14. );
  15. };

四、开箱即用的前端开发框架

1、UMI

umi是一个企业级的前端开发框架,开箱即用,内置路由、构建、部署、测试、Lint 等。内置微前端、数据流、权限、国际化、icons 方案、埋点、antd、请求、CSS 方案、图表等最佳实践。有完备的路由,基于 React Router 6,类 Remix,支持嵌套、动态、动态可选、预加载、基于路由的请求优化等。

Umi 有很多非常有意思的特性,比如:

1、企业级,在安全性、稳定性、最佳实践、约束能力方面会考虑更多
2、插件化,啥都能改,Umi 本身也是由插件构成
3、MFSU,比 Vite 还快的 Webpack 打包方案
4、基于 React Router 6 的完备路由
5、默认最快的请求
6、SSR & SSG
7、稳定白盒性能好的 ESLint 和 Jest
8、React 18 的框架级接入
9、Monorepo 最佳实践
...

官方文档:/

2、Ant Design Pro

Ant Design Pro 以 umi 作为脚手架,启动和开发与 umi 基本相同,是基于 Ant Design 和 umi 的封装的一整套企业级中后台前端/设计解决方案,为我们提供完整的脚手架,涉及国际化权限,mock,数据流网络请求等各个方面。为这些中后台中常见的方案提供了最佳实践来减少学习和开发成本。

官方使用文档:开始使用 - Ant Design Pro

学习参考文档

React 官方中文文档 – 用于构建用户界面的 JavaScript 库

Create React App 中文文档 · 通过运行一个命令来建立现代Web应用程序。

Introduction | React Router 中文文档

React Router: Declarative Routing for

React Router 使用教程 - 阮一峰的网络日志

Ant Design - The world's second most popular React UI framework

Redux中文文档

Redux 入门教程(一):基本用法 - 阮一峰的网络日志

Redux 入门教程(三):React-Redux 的用法 - 阮一峰的网络日志

axios中文网|axios API 中文文档 | axios

Vue CLI4.0 webpack配置属性——outputDir、assetsDir、indexPath_Jay的博客-****博客

Vue CLI4.0 webpack配置属性——publicPath_Jay的博客-****博客