克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

基于 vite 构建 react18 应用

vite 初始化 React 项目

  1. yarn create vite 选择 react 项目
  2. yarn install安装依赖
  3. yarn dev启动项目

制定项目规范 eslint+prettier

  1. yarn add eslint -D
  2. npx eslint --init 按照提示选择,init 命令会自动生成 .eslintrc.js,关键选择如下:
    选项 解释
    To check syntax and find problems 只检查语法和错误
    JavaScript modules (import/export) 项目使用的模块化规范
    Browser Node 项目运行环境
    eslint-plugin-react 检查 react 的 eslint 插件
  3. yarn add prettier eslint-config-prettier eslint-plugin-prettier -D ,其中 eslint-config-prettier 和 eslint-plugin-prettier 是为了解决 eslint 和 prettier 的冲突使用的插件
  4. .eslintrc 中,extend 中添加 "prettier" 解决 eslint 和 prettier 的冲突
  5. 创建 .prettierrc,配置
    {
      "arrowParens": "avoid",
      "trailingComma": "none",
      "singleQuote": true
    }
    
  6. yarn add lint-staged husky -D,husky 是操作 git hooks 的工具,用来在 git commit 的时候,会使用 lint-stage 执行 eslint 和 prettier,对代码进行语法检查和格式化
  7. npm set-script prepare "husky install" 在 package.json 中添加脚本
  8. yarn prepare 初始化 husky,会在根目录创建 .husky 文件夹
  9. npx husky add .husky/pre-commit "npx lint-staged" 在 commit 时执行 npx lint-staged 指令
  10. 在 package.json 中添加如下代码,指定 lint-stage 时要执行的命令
    "lint-staged": {
      "*.{js,jsx}": [
        "eslint --fix",
        "prettier --write"
      ],
      "*.{html,css,less,json,md}": [
        "prettier --write"
      ]
    }
    

配置 vite

  1. yarn add vite-plugin-imp 用于解决 antd 组件样式的按需加载

  2. 分别新建 .env.development.env.staging.env.production ,环境变量文件,配置开发,预发布,生产环境的接口和 CDN 静态资源路径, 这里测试(BETA)环境静态资源路径(CDN)设置为了 vite preview 的默认端口 4173

    • .env.development
      VITE_PROXY_URL = http://localhost:5000/
      VITE_CDN_URL = /
      
    • .env.staging
      VITE_PROXY_URL = http://localhost:5000/
      VITE_CDN_URL = http://localhost:4173/
      
    • .env.productioion
      VITE_PROXY_URL = http://localhost:5000/
      VITE_CDN_URL = http://localhost:4173/
      
  3. 配置 vite.config.js

    import { defineConfig, loadEnv } from 'vite';
    import react from '@vitejs/plugin-react';
    import path from 'path';
    
    // https://vitejs.dev/config/
    
    export default ({ mode }) => {
      const env = loadEnv(mode, process.cwd());
    
      return defineConfig({
        base: env.VITE_CDN_URL, // 不同环境下静态资源的路径前缀
        assetsInclude: ['**/*.glb'], // 指定额外的文件作为静态资源处理
        plugins: [react()],
        css: {
          // 配置css modules
          modules: {
            generateScopedName: '[local]_[hash:base64:5]',
            hashPrefix: 'prefix'
          },
          preprocessorOptions: {
            less: {
              // 全局变量 (官方建议只引入全局less变量, 其余样式会导致重复引入)
              additionalData: '@import "@/assets/styles/global.less";',
              // 支持内联 JavaScript
              javascriptEnabled: true
            }
          }
        },
        resolve: {
          alias: {
            '~': path.resolve(__dirname, './'), // 根路径
            '@': path.resolve(__dirname, 'src') // src 路径
          }
        },
        server: {
          open: true,
          proxy: {
            //遇见/cyberUnicorn-M前缀的请求,就会触发该代理配置
            '/cyberUnicorn-M': {
              target: env.VITE_PROXY_URL, //动态判断不同环境请求转发给谁
              changeOrigin: true, //控制服务器收到的请求头中Host的值 true时为服务器端口号 false为客户端端口号 一般设置true为了避免服务器判定为跨域
              rewrite: path => path.replace(/^\/cyberUnicorn-M/, '') //重写请求路径(必须) 去除服务器收到的请求url里带有/cyberUnicorn-M前缀找不到接口报错
            }
          }
        }
      });
    };
    
  4. 配置package.json,在 script 中加入 "build:staging": "vite build --mode staging" 在开发环境下,执行 yarn dev 会默认加载 .env.development,执行 yarn build 会默认加载*.env.productioion*,所以 staging 模式通过传递 --mode 选项标志来覆盖命令使用的默认模式

注意事项:

在生产环境中,这些环境变量会在构建时被静态替换,因此,在引用它们时请使用完全静态的字符串。动态的 key 将无法生效。例如,动态 key 取值 import.meta.env[key] 是无效的。

所以这里在之前导致的在预发布环境和生产环境接口 500 的 bug,是因为 proxy 的 target 动态取值写了如下代码,导致生产环境 proxy 失效:

// 错误
proxy: {
  '/cyberUnicorn-M': {
    target: env[`VITE_${mode.toUpperCase()}_API`],
    changeOrigin: true,
    rewrite: path => path.replace(/^\/cyberUnicorn-M/, '')
  }
}

引入路由

  1. yarn add react-router-dom@latest 安装最新版的 react-router-dom v6 版本

  2. 配置路由表,新建 router.jsx

    import React, { lazy } from 'react';
    import { Navigate } from 'react-router-dom';
    
    const Exception = lazy(() => import('@/components/Exception'));
    
    const Login = lazy(() => import('@/pages/Login'));
    const Manage = lazy(() => import('@/pages/Manage'));
    const Home = lazy(() => import('@/pages/Home'));
    const Category = lazy(() => import('@/pages/Category'));
    const Product = lazy(() => import('@/pages/Product'));
    const UserManage = lazy(() => import('@/pages/UserManage'));
    const AccountCenter = lazy(() => import('@/pages/AccountCenter'));
    const AccountSettings = lazy(() => import('@/pages/AccountSettings'));
    const Role = lazy(() => import('@/pages/Role'));
    const Bar = lazy(() => import('@/pages/Bar'));
    const Line = lazy(() => import('@/pages/Line'));
    const Pie = lazy(() => import('@/pages/Pie'));
    
    export default [
      // * 为最低优先级 一般用于找不到匹配路由 展示404页面
      {
        path: '*',
        element: <Exception />
      },
      // Navigate组件用于重定向 一定会引起页面的重新渲染 to属性不可以省略
      {
        path: '/',
        element: <Navigate to="/manage/home" replace={true} />
      },
      {
        path: '/login',
        element: <Login />
      },
      {
        path: '/manage',
        element: <Manage />,
        //  子级路由不需要再写 "/"
        children: [
          {
            path: 'home',
            element: <Home />
          },
          {
            path: 'commodities',
            children: [
              {
                path: 'category',
                element: <Category />
              },
              {
                path: 'product',
                element: <Product />
              }
            ]
          },
          {
            path: 'user',
            children: [
              {
                path: 'userManage',
                element: <UserManage />
              },
              {
                path: 'accountCenter',
                element: <AccountCenter />
              },
              {
                path: 'accountSettings',
                element: <AccountSettings />
              }
            ]
          },
          {
            path: 'role',
            element: <Role />
          },
          {
            path: 'charts',
            children: [
              {
                path: 'bar',
                element: <Bar />
              },
              {
                path: 'line',
                element: <Line />
              },
              {
                path: 'Pie',
                element: <Pie />
              }
            ]
          }
        ]
      }
    ];
    
  3. main.jsx 中,用 BrowserRouter 包裹 App 组件

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter } from 'react-router-dom';
    import App from './App';
    
    //  StrictMode严格模式在开发环境下为了检测副作用影响会导致组件重复渲染 正式环境没有影响
    ReactDOM.createRoot(document.getElementById('root')).render(
      <React.StrictMode>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </React.StrictMode>
    );
    
  4. 在 App 组件用 useRoutes hook 渲染路由

    import React from 'react';
    import { useRoutes } from 'react-router-dom';
    import routesConfig from '~/config/router';
    
    exprot default function App() {
      const routes = useRoutes(routesConfig);
    
      return routes
    }
    
  5. 渲染多级嵌套路由, 使用 Outlet 组件,告知子路由应该渲染在什么位置

    import React from 'react';
    import { Outlet, NavLink } from 'react-router-dom';
    
    export default function Manage() {
      return (
        <div>
          <div className="nav">
            <div className="nav-item">
              <NavLink to="/manage/user/accountCenter">accountCenter</NavLink>
            </div>
            <div className="nav-item">
              <NavLink to="/manage/user/accountSettings">
                accountSettings
              </NavLink>
            </div>
            <div className="content">
              <Outlet />
            </div>
          </div>
        </div>
      );
    }
    

react-intl 结合 antd 实现国际化

  1. yarn add react-intl

  2. src 下新建 locales 文件夹,在文件夹下新建 en-US.js zh-CN.js 文件

    // zh-CN.js
    const zh_CN = {
      'intl.SEARCH': '查询',
      'intl.SELECT': '选择',
      'intl.SAVE': '保存',
      'intl.ALL': '全部',
      'intl.YES': '是',
      'intl.NO': '否',
      'intl.OK': '确定',
      'intl.CANCEL': '取消',
      'intl.CLOSE': '关闭'
    };
    
    export default zh_CN;
    
    // en-US.js
    const en_US = {
      'intl.SEARCH': 'Search',
      'intl.SELECT': 'Select',
      'intl.SAVE': 'Save',
      'intl.ALL': 'All',
      'intl.YES': 'Yes',
      'intl.NO': 'No',
      'intl.OK': 'Ok',
      'intl.CANCEL': 'Cancel',
      'intl.CLOSE': 'Close'
    };
    
    export default en_US;
    
  3. App.jsx 中:

    import { ConfigProvider } from 'antd';
    import { IntlProvider } from 'react-intl';
    import { useRoutes } from 'react-router-dom';
    import routesConfig from '~/config/router';
    import ant_en_US from 'antd/es/locale/en_US';
    import ant_zh_CN from 'antd/es/locale/zh_CN';
    import intl_en_US from '@/locales/en-US';
    import intl_zh_CN from '@/locales/zh-CN';
    
    export default function App() {
      const routes = useRoutes(routesConfig);
      // ConfigProvider 为antd的全局配置 我们可以利用state改变locale 传入它们的Provider更改全局语言设置
      return (
        <ConfigProvider locale={ant_zh_CN}>
          <IntlProvider locale="zh-CN" messages={intl_zh_CN}>
            {routes}
          </IntlProvider>
        </ConfigProvider>
      );
    }
    
  4. 在组件中使用:

    • 使用 FormattedMessage 组件

      import { FormattedMessage, FormattedDate } from 'react-intl';
      export default function Home() {
        return (
          <>
            <FormattedMessage id="intl.Search" />
            // input文本框中placeholder文字的国际化
            <FormattedMessage id="intl.ENTER_TML_NAME">
              {text => <Input placeholder={text} />}
            </FormattedMessage>
            // 国际化日期
            <FormattedDate value={Date.now()} year="numeric" />
          </>
        );
      }
      
    • 使用 useIntl hook

      import { useIntl } from 'react-intl';
      
      export default function Home() {
        const intl = useIntl();
      
        return (
          <>
            <span>{intl.formatMessage({ id: 'intl.Search' })}</span>
            // input文本框中placeholder文字的国际化
            <Input
              placeholder={intl.formatMessage({ id: 'intl.ENTER_TML_NAME' })}
            />
          </>
        );
      }
      
  5. 在非组件中使用

    import { createIntl, createIntlCache } from 'react-intl';
    import { message } from 'antd';
    import intl_en_US from '@/locales/en-US';
    import intl_zh_CN from '@/locales/zh-CN';
    
    const locale = navigator.language;
    
    const cache = createIntlCache();
    // 创建intl实例
    const intl = createIntl(
      {
        locale,
        messages: locale.indexOf('zh') < 0 ? intl_en_US : intl_zh_CN
      },
      cache
    );
    
    message.error(intl.formatMessage({ id: 'Message.401' }));
    
  6. 注意事项: 如果使用了 antd 的时间类组件,需要引入 import 'moment/dist/locale/zh-cn' 否则会出现中英混合的情况,如果使用的时间类的组件多,可在 App.jsx 全局引入,如果只在一个组件里使用,可以在当前组件引入,如果没有使用则不引入

高德地图天气接口的使用

  • 问题:在本地服务器给高德地图天气接口发送请求会产生跨域的问题
  • 解决方式一:在vite.config.js文件中配置代理,解决跨域,发送请求时带上配置的前缀axios.get('/amap-weather/v3/weather/weatherInfo', params: { }),配置代码如下:
"/amap-weather": {
    target: "https://restapi.amap.com/",
    changeOrigin: true,
    pathRewrite: { "^/amap-weather": "" }
}
  • 解决方式二:使用 jsonp 发送请求解决跨域,执行yarn add jsonp安装依赖,引入import jsonp from 'jsonp',接口代码如下:
export const reqWeather = () => {
  return new Promise(resolve => {
    jsonp(
      `https://restapi.amap.com/v3/weather/weatherInfo?key=${AMAP_KEY}&city=${CITY_ADCODE}&extensions=base&output=JSON`,
      (err, data) => {
        if (err) {
          message.warn('请求天气接口失败,请联系管理员');
          return new Promise(() => {});
        } else {
          if (data.status === '0') {
            message.warn('天气数据返回错误,请联系管理员');
          } else {
            resolve(data.lives[0]);
          }
        }
      }
    );
  });
};
MIT License Copyright (c) 2022 郝文俊 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

基于vite 4构建的react为主技术栈,antd 5为UI,实现的后台管理SPA简易框架 展开 收起
JavaScript 等 5 种语言
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化