基于Vite快速建立react项目,以及实现自适应适配和axios的封装

内容分享3天前发布
1 0 0
一、创建react项目  (node 版本 20)

这里我们使用react+js+react-router-dom 5+redux+axio

1.创建 vreact项目,安装创建项目 (这里我创建的是react19.2.0)

npm init vite@latest

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

启动项目 

npm install

npm run dev

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

一般情况下,我们需要按照需要配置以下目录结构,如状态管理、路由、静态资源、组件、页面、工具包,这些都需要在src下有一席之地,按照我个人的习惯,我预先建立了比较常见的以下目录:

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

 2.安装其他插件

2.1配置resolve.alias



//vite.congit.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
 
// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve:{
    alias:{
      '@': path.resolve(__dirname, './src')
    }
  }
})

2.2安装npm i react-router-dom,,注意这里的版本是v5

npm install react-router-dom@7.10.1

在views中简单写两个页面,用来切换路由使用在router文件夹中编写路由文件index.js,导出路由模块



//home.jsx
import React from 'react';
import { useNavigate } from 'react-router-dom';
 
const Home = () => {
    let navigate = useNavigate();
  return (
    <div style={{ padding: '20px' }}>
      <h1>首页</h1>
      <p>这是首页内容,用于路由跳转测试。</p>
      <div onClick={() => navigate('/find')} style={{ cursor: 'pointer', color: 'blue', textDecoration: 'underline' }}>前往发现页面</div>
    </div>
  );
};
 
export default Home;


//find.jsx
import React from 'react';
import { useNavigate } from 'react-router-dom';
 
const Find = () => {
  const navigate = useNavigate();
  return (
    <div style={{ padding: '20px' }}>
      <h1>发现页面</h1>
      <p>这是发现页面内容,用于路由跳转测试。</p>
      <div onClick={() => navigate('/')} style={{ cursor: 'pointer', color: 'blue', textDecoration: 'underline' }}>返回首页</div>
    </div>
  );
};
 
export default Find;

在app.jsx中将路由模块导入

import { HashRouter, Routes, Route } from 'react-router-dom'

import { routes } from './router/index.jsx'



//app.jsx
import React from 'react'
import './App.css'
 
import { HashRouter, Routes, Route } from 'react-router-dom'
import { routes } from './router/index.jsx'
 
function App() {
  return (
    <>
      <HashRouter>
        <Routes>
          {routes.map((router) => (
            <Route key={router.path} {...router} />
          ))}
        </Routes>
      </HashRouter>
    </>
  )
}
 
export default App

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

2.3 状态管理,前端框架三剑客之一,这里我们将为我们的项目配置redux状态管理

安装以下依赖包npm install reduxnpm install react-reduxnpm install @reduxjs/toolkitnpm install redux-thunknpm install esbuild

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

创建一个Redux切片(Slice)并在store中进行导入和配置

切片是应用中单个功能的Redux reducer 逻辑和 action 的集合,在这里做了一个计数功能的切片以供学习使用,代码如下



//counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
 
// 初始状态
const initialState = {
  value: 0,
};
 
// 创建slice
export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      // Redux Toolkit 允许我们在 reducers 中直接修改状态
      // 它不会真正修改状态,而是使用 Immer 库来创建状态的不可变副本
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});
 
// 导出 actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
 
// 导出 reducer
export default counterSlice.reducer;

创建store

你可以在你的src目录中创建一个名为store的文件夹并在里面新建一个index.js文件,并把刚才的redux切片导入

在该文件中你需要有以下的结构



// index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
 
// 配置并创建store
export const store = configureStore({
  reducer: {
    // 这里可以添加多个reducer
    counter: counterReducer,
  },
});

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

把配置好的store与应用进行连接

在应用的main.jsx中添加如下代码



//main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
 
// Redux
import { Provider } from 'react-redux'
import { store } from './store/index.js'
 
createRoot(document.getElementById('root')).render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>,
)

使用和修改store中的state

其实到上一步,redux已经在项目中配置好了,这一步我们主要是看看store是否配置成功和测试一下使用方法,随便找一个应用中的组件:



import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux'; //引入两个钩子函数
//在home.jsx中使用
import { increment, decrement, incrementByAmount } from '@/store/counterSlice'; //按需引入action
 
const Home = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const count = useSelector((state) => state.counter.value);
 
  return (
    <div style={{ padding: '20px' }}>
      <h1>首页</h1>
      <p>这是首页内容,用于路由跳转测试。</p>
      <div onClick={() => navigate('/find')} style={{ cursor: 'pointer', color: 'blue', textDecoration: 'underline', marginBottom: '20px' }}>前往发现页面</div>
      
      {/* Redux 计数器示例 */}
      <div style={{ marginTop: '30px', padding: '20px', border: '1px solid #ccc', borderRadius: '8px', maxWidth: '300px' }}>
        <h2>Redux 计数器</h2>
        <p>计数: {count}</p>
        <div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
          <button onClick={() => dispatch(increment())}>+1</button>
          <button onClick={() => dispatch(decrement())}>-1</button>
          <button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
        </div>
      </div>
    </div>
  );
};
 
export default Home;
基于Vite快速建立react项目,以及实现自适应适配和axios的封装二、页面实现适配

1. 安装npm install -D sass-embedded CSS预处理器

这里是:react中css写法以及用法大全 

基于Vite快速建立react项目,以及实现自适应适配和axios的封装



//home.module.scss
.home{
    padding: 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
    h1{
        font-size: 32px;
    }
    p{
        font-size: 18px;
    }
    h2{
        font-size: 18px;
    }
}

2. 使用amfe-flexible和postcss-pxtorem实现适配

# 安装核心依赖
npm install amfe-flexible postcss-pxtorem autoprefixer postcss-import postcss-url --save-dev

a.在vite.config.js中进行配置



//vit.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import postcssImport from 'postcss-import';
import postcssUrl from 'postcss-url';
import autoprefixer from 'autoprefixer';
import pxtorem from 'postcss-pxtorem';
 
// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve:{
    alias:{
      '@': path.resolve(__dirname, './src')
    }
  },
  // 配置 CSS
  css: {
    postcss: {
      plugins: [
        // 处理 @import 和 url() 路径
        postcssImport(),
        postcssUrl(),
        
        // 自动添加浏览器前缀
        autoprefixer({
          overrideBrowserslist: [
            'last 5 version',
            '>1%',
            'ie >=8'
          ]
        }),
        
        // px 转 rem(核心适配插件)
        pxtorem({
          rootValue: 192, // 1920设计稿除以10
          minPixelValue: 1, // 最小转换值,1px及以上转换
          unitPrecision: 6, // 转换后的小数位数
          propList: ['*'], // 所有属性都转换
          selectorBlackList: ['aaa-'], // 匹配不被转换的选择器
          replace: true, // 直接替换而不是添加备用
          mediaQuery: false, // 媒体查询中的px不转换
          exclude: /node_modules/i // 排除 node_modules
        })
      ]
    }
  },
  server:{
        proxy: {
            '/DreamOne': {
                target: 'http://10.3.4.174:8080/DreamOne/',
                changeOrigin: true,
                rewrite: (path) =>  {
                    return  path.replace(/^/DreamOne/, '')
                },
            }
        }
    }
})

也可以单独维护 
postcss.config.js,把他引入到vite.cofig.js中就行



// postcss.config.js
const postcssImport = require('postcss-import');
const postcssUrl = require('postcss-url');
const autoprefixer = require('autoprefixer');
const pxtorem = require('postcss-pxtorem');
 
module.exports = {
  plugins: [
    postcssImport(),
    postcssUrl(),
    autoprefixer({
      overrideBrowserslist: [
        'last 5 version',
        '>1%',
        'ie >=8'
      ]
    }),
    pxtorem({
      rootValue: 192,
      minPixelValue: 1,
      unitPrecision: 6,
      propList: ['*'],
      selectorBlackList: ['aaa-'],
      replace: true,
      mediaQuery: false,
      exclude: /node_modules/i
    })
  ]
};


// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
  plugins: [react()],
  css: {
    postcss: './postcss.config.js'
  }
});

b.引入 amfe-flexible,在项目入口文件 
src/main.jsx
 或 
src/main.tsx
 中:

import 'amfe-flexible'; // 引入自适应方案

c.创建内联样式转换工具

由于内联样式不会被 PostCSS 处理,需要手动转换:



// src/utils/px2rem.js
 
/**
 * 将px转换为rem(基于192设计稿)
 * @param {number|string} px - px值
 * @returns {string} rem值
 */
export const px2rem = (px) => {
  if (typeof px === 'string') {
    // 处理带单位的情况
    const match = px.match(/^(d+(.d+)?)(px)?$/);
    if (match) {
      const num = parseFloat(match[1]);
      return `${num / 192}rem`;
    }
    return px; // 如果不是纯数字或px单位,原样返回
  }
  return `${px / 192}rem`;
};
 
/**
 * 批量转换样式对象中的px到rem
 * @param {Object} style - 样式对象
 * @param {Array} excludeProps - 排除的属性列表
 * @returns {Object} 转换后的样式对象
 */
export const styleToRem = (style, excludeProps = []) => {
  const result = {};
  const pxProperties = [
    'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',
    'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
    'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
    'top', 'right', 'bottom', 'left',
    'fontSize', 'lineHeight', 'letterSpacing',
    'border', 'borderWidth', 'borderRadius', 'borderTopLeftRadius', 
    'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius',
    'gap', 'rowGap', 'columnGap',
    'outline', 'outlineWidth',
    'textIndent'
  ];
  
  Object.keys(style).forEach(key => {
    const value = style[key];
    
    // 检查是否在排除列表中
    if (excludeProps.includes(key)) {
      result[key] = value;
      return;
    }
    
    // 检查是否是数字
    if (pxProperties.includes(key) && typeof value === 'number') {
      result[key] = px2rem(value);
    } 
    // 检查是否是带px的字符串
    else if (pxProperties.includes(key) && 
             typeof value === 'string' && 
             /^-?d+(.d+)?px$/.test(value)) {
      const pxValue = parseFloat(value);
      result[key] = px2rem(pxValue);
    } 
    // 其他情况直接赋值
    else {
      result[key] = value;
    }
  });
  
  return result;
};

在home.jsx中使用



//home/home.jsx
import React from 'react';
import styles from './home.module.scss';
import { useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux'; //引入两个钩子函数
import { increment, decrement, incrementByAmount } from '@/store/counterSlice'; //按需引入action
import { px2rem, styleToRem } from '@/utils/px2rem';
 
const Home = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const count = useSelector((state) => state.counter.value);
 
  return (
    <div className={styles.home}>
      <h1>首页</h1>
      <p>这是首页内容,用于路由跳转测试。</p>
      <div onClick={() => navigate('/find')} style={{ cursor: 'pointer', color: 'blue', textDecoration: 'underline', marginBottom: px2rem(20), fontSize: px2rem(18) }}>前往发现页面</div>
      
      {/* Redux 计数器示例 */}
      <div style={{ marginTop: px2rem(30), padding: px2rem(20), border: '1px solid #ccc', borderRadius: px2rem(8), maxWidth: px2rem(300) }}>
        <h2>Redux 计数器</h2>
        <p>计数: {count}</p>
        <div style={{ display: 'flex', gap: px2rem(10), marginBottom: px2rem(10) }}>
          <button style={{ padding: px2rem(5), borderRadius: px2rem(4), border: '1px solid #ccc', fontSize: px2rem(16) }} onClick={() => dispatch(increment())}>+1</button>
          <button style={{ padding: px2rem(5), borderRadius: px2rem(4), border: '1px solid #ccc', fontSize: px2rem(16) }} onClick={() => dispatch(decrement())}>-1</button>
          <button style={{ padding: px2rem(5), borderRadius: px2rem(4), border: '1px solid #ccc', fontSize: px2rem(16) }} onClick={() => dispatch(incrementByAmount(5))}>+5</button>
        </div>
      </div>
    </div>
  );
};
 
export default Home;

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

三、axios的封装 

安装 

npm install axios qs umi-request js-base64
npm install antd

a.在utils创建config.js、dsfRequest.js、api2.js文件

这里注意import.meta.env.VITE_API_URL是.env环境变量中的会在第四点配置



//config.js
//配置服务端地址 
const serverUrl = {
    doPost: "/skytraffic/doPost",
    doAction: "/skytraffic/doAction",
    WebRoot: "/skytraffic"
}
 
export {
    serverUrl
}


//dsfRequest.js
/**
 * request 网络请求工具
 * 更详细的 api 文档: https://github.com/umijs/umi-request
 */
import { extend } from 'umi-request';
//https://www.npmjs.com/package/js-base64
import { Base64 } from 'js-base64';
import { message } from 'antd'
import { serverUrl } from './config'
 
const codeMessage = {
    200: '服务器成功返回请求的数据。',
    201: '新建或修改数据成功。',
    202: '一个请求已经进入后台排队(异步任务)。',
    204: '删除数据成功。',
    400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
    401: '用户没有权限(令牌、用户名、密码错误)。',
    403: '用户得到授权,但是访问是被禁止的。',
    404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
    406: '请求的格式不可得。',
    410: '请求的资源被永久删除,且不会再得到的。',
    422: '当创建一个对象时,发生一个验证错误。',
    500: '服务器发生错误,请检查服务器。',
    502: '网关错误。',
    503: '服务不可用,服务器暂时过载或维护。',
    504: '网关超时。',
}
 
/**
 * 异常处理程序
 */
const errorHandler = function(error) {
    const { response } = error
    if (response && response.status) {
        const errorText = codeMessage[response.status] || response.statusText
        const { status, url } = response
        message.error(`请求错误 ${status}: ${url}`)
    }
    return response
}
 
/**
 * 配置request请求时的默认参数
 */
const request = extend({
    errorHandler, // 默认错误处理
    credentials: 'include', // 默认请求是否带上cookie
})
 
// request拦截器, 改变url 或 options.
request.interceptors.request.use((url, options) => {
 
        options.headers = options.headers || {};
        options.headers.code = 'MOBILE';
        let devApiUrl = process.env.VUE_APP_BASE_API
        if (url.indexOf("?") > -1) {
            //处理自动识别上传地址,带问号的都视为原请求方式
            return ({
                url: `${devApiUrl+serverUrl.WebRoot+(url.indexOf('/')==0?"":"/")+url}`,
                options: options,
            })
        }
        let method = ""
        const lastIndex = url.lastIndexOf(".")
        if (lastIndex > 0) {
            const lastName = url.substr(lastIndex)
            if (lastName == ".xml" || lastName == ".bl") {
                method = "post"
            } else {
                method = "action"
            }
        } else {
            method = "post"
        }
 
 
 
        options.data = options.data || {}
            // const headers = options.headers;
            // headers.code = 'MOBILE';
        const requestData = createRequest(url, JSON.stringify(options.data))
        const reqUrl = method.toLowerCase() == "post" ? serverUrl.doPost : serverUrl.doAction
            // const { NODE_ENV } = process.env
        return ({
            url: `${devApiUrl+reqUrl}`,
            options: { method: 'post', requestType: 'json', data: requestData, headers: options.headers },
        })
    })
    // response拦截器, 处理response
request.interceptors.response.use((response, options) => {
        //response.headers.append('code', 'MOBILE');
        return response
    })
    // 克隆响应对象做解析处理
request.interceptors.response.use(async(response) => {
        const data = await response.clone().json();
        if (!data.Result) {
            return response
        }
        if (data && data.Result && data.Result.Success) {
            return data.Result.Data;
        } else {
            message.error(`请求错误===》` + data.Result.Data)
        }
        return response;
    })
    /**
     * dsf框架请求后台组织xml格式进行base64编码啊
     */
const createRequest = function(path,
    content,
    requestformat,
    responseformat,
    options,
    expro, enCode) {
 
    requestformat = requestformat || 'JSON'
    responseformat = responseformat || 'JSON'
    content = content || ''
    options = options || {}
    let d = content
    let b = ""
    let isCache = false
    let isHead = true
    if (options) {
        if (typeof(options) == "object") {
            isCache = options.isCache || false
            isHead = options.isHead != false ? true : false
        } else {
            isCache = true
        }
    }
    if (isCache) {
        b = `<Cache type='MEMORY' period='6000000000000'></Cache>`;
    }
    if (typeof(content) == "string" && content.indexOf("<Data>") < 0) {
        if (enCode != false) {
            if (requestformat == "JSON") {
                //alert("test");
                //let data = $("<Data></Data>").text(content);
                //content = data.html();
            }
            //let bb = "";
        }
        d = `<Data>${content}</Data>`
    }
 
    expro = expro ? `exinfo="${expro}" ` : ``
    let name = options.name || ""
 
    let request = `<Request ${name ? 'name="' + name + '"' : ""} action="${path}" request="${requestformat}" response="${responseformat}" ${!isHead ? 'nohead="true"' : ''} ${expro}>${b + d}</Request>`
 
    if (options.base64 != false) {
        return Base64.encode(request)
    } else {
        return request
    }
}
 
// Base64.encode('dankogai');  // ZGFua29nYWk=
// Base64.encode('小飼弾');    // 5bCP6aO85by+
// Base64.encodeURI('小飼弾'); // 5bCP6aO85by-
 
// Base64.decode('ZGFua29nYWk=');  // dankogai
// Base64.decode('5bCP6aO85by+');  // 小飼弾
// // note .decodeURI() is unnecessary since it accepts both flavors
// Base64.decode('5bCP6aO85by-');  // 小飼弾
 
export default request


//api2.js
import axios from 'axios';
import request from '../utils/dsfRequest.js';
import qs from 'qs';
 
 
export const getJson = function (method) {
    console.log(import.meta.env);
    
    return new Promise((resolve, reject) => {
        axios({
            method: 'get',
            url: method,
            dataType: "json",
            crossDomain: true,
            cache: false
        }).then(res => {
            resolve(res)
        }).catch(error => {
            reject(error)
        })
    })
}
export const getServerData = function (url = "", params = {}) {
    return new Promise((resolve, reject) => {
        request(url, { data: params }).then(res => {
            resolve(res)
        }).catch(err => {
            resolve(err)
        })
    })
}
 
 
export const http = {
    get: function (url, params, options) {
        if (import.meta.env.VITE_NODE_ENV == "production") {
            url =  import.meta.env.VITE_API_URL + url
        }else{
            url='/DreamWeb'+url
        }
        let opts = {
            params: params,
            headers: {},
            paramsSerializer: function (params) {
                return qs.stringify(params, {
                    arrayFormat: "repeat"
                });
            }
        };
        opts.transformRequest = [
            function (data) {
                let ret = "";
                for (let it in data) {
                    ret +=
                        encodeURIComponent(it) + "=" + encodeURIComponent(data[it]) + "&";
                }
                return ret;
            }
        ];
        opts = Object.assign(opts, options || {});
        let p = axios.get(url, opts);
        return p;
    },
    post: function (url, params, options) {
        if (import.meta.env.VITE_NODE_ENV == "production") {
            url =  import.meta.env.VITE_API_URL + url
        }else{
            url='/DreamWeb'+url
        }
        let configContentType =
            options && options.headers && options.headers["Content-Type"] ?
                options.headers["Content-Type"] :
                "";
        let opts = {
            headers: {}
        };
        opts.transformRequest = [
            function (data) {
                let ret;
                if (configContentType.includes("multipart/form-data")) {
                    ret = data;
                } else if (configContentType.includes("application/json")) {
                    ret = JSON.stringify(data);
                } else {
                    ret = "";
                    for (let it in data) {
                        ret +=
                            encodeURIComponent(it) + "=" + encodeURIComponent(data[it]) + "&";
                    }
                }
                return ret;
            }
        ];
        opts = Object.assign(opts, options || {});
        let p = axios.post(url, params, opts);
        return p;
    }
}

b.在home页面中使用

注意这里的json文件需要再public文件中,要不打包的时候打不上



//home.jsx
import {useEffect} from 'react';
import styles from './home.module.scss';
import { useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux'; //引入两个钩子函数
import { increment, decrement, incrementByAmount } from '@/store/counterSlice'; //按需引入action
import { px2rem, styleToRem } from '@/utils/px2rem';
import { getJson } from '@/utils/api2.js';
 
const Home = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const count = useSelector((state) => state.counter.value);
 
  useEffect(()=>{
      getJson('/test.json').then(res=>{
          console.log(res,"++++")
      })
  },[])
 
  return (
    <div className={styles.home}>
      <h1>首页</h1>
      <p>这是首页内容,用于路由跳转测试。</p>
      <div onClick={() => navigate('/find')} style={{ cursor: 'pointer', color: 'blue', textDecoration: 'underline', marginBottom: px2rem(20), fontSize: px2rem(18) }}>前往发现页面</div>
      
      {/* Redux 计数器示例 */}
      <div style={{ marginTop: px2rem(30), padding: px2rem(20), border: '1px solid #ccc', borderRadius: px2rem(8), maxWidth: px2rem(300) }}>
        <h2>Redux 计数器</h2>
        <p>计数: {count}</p>
        <div style={{ display: 'flex', gap: px2rem(10), marginBottom: px2rem(10) }}>
          <button style={{ padding: px2rem(5), borderRadius: px2rem(4), border: '1px solid #ccc', fontSize: px2rem(16) }} onClick={() => dispatch(increment())}>+1</button>
          <button style={{ padding: px2rem(5), borderRadius: px2rem(4), border: '1px solid #ccc', fontSize: px2rem(16) }} onClick={() => dispatch(decrement())}>-1</button>
          <button style={{ padding: px2rem(5), borderRadius: px2rem(4), border: '1px solid #ccc', fontSize: px2rem(16) }} onClick={() => dispatch(incrementByAmount(5))}>+5</button>
        </div>
      </div>
    </div>
  );
};
 
export default Home;

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

四、根据不同.env环境配置文件打包

这里以.env.prod文件为例

a.创建.env.prod文件



#.env.prod 文件
# 本地环境接口地址(这里是使用了代理,解决跨域问题)
 
#所在环境
VITE_NODE_ENV = 'production'
 
# 业务中台本地环境接口地址
VITE_API_URL = '/DreamWeb'
 
# 打包文件名
VITE_APP_NAME = 'dist_pc'

b.在vite.config.js中进行打包配置



//vite.config.js
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import postcssImport from 'postcss-import';
import postcssUrl from 'postcss-url';
import autoprefixer from 'autoprefixer';
import pxtorem from 'postcss-pxtorem';
 
const Timestamp = new Date().getTime();//随机时间戳
// https://vite.dev/config/
export default defineConfig(({mode})=>{
  const env = loadEnv(mode, process.cwd());
  console.log(env);
  let fileName = 'dist'
	// 兼容性,以防打包崩溃
	fileName = env.VITE_APP_NAME
  return {
    base: '',//项目名称开发或生产环境服务的公共基础路径。
    plugins: [react()],
    resolve:{
      alias:{
        '@': path.resolve(__dirname, './src')
      }
    },
    server:{
          proxy: {
              '/DreamOne': {
                  target: 'http://10.3.4.174:8080/DreamOne/',
                  changeOrigin: true,
                  rewrite: (path) =>  {
                      return  path.replace(/^/DreamOne/, '')
                  },
              }
          }
    },
     build: {
      outDir: fileName, // 打包后文件包名称
      sourcemap: false,//控制是否生成源映射文件
      target: ['ios11', 'es2015'],//指定目标浏览器和 JavaScript 版本。解释:这里使用了 ios11 和 es2015,表示你希望构建的代码在 iOS 11 及更高版本的浏览器中运行,并且使用 ES2015 的语法。这有助于优化构建输出,使其更适应目标环境。
      rollupOptions: {
        // 使用了模板字符串和 ${Timestamp} 变量,这是为了在文件名中添加构建时的时间戳,以避免浏览器缓存问题。这样每次构建都会生成带有新时间戳的文件名,确保文件更新后不受缓存影响
        output: {
          chunkFileNames: `static/js/[name].[hash]${Timestamp}.js`,//配置代码拆分后的 chunk 文件名规则。
          entryFileNames: `static/js/[name].[hash]${Timestamp}.js`,//配置入口文件的输出文件名规则。
          assetFileNames: `static/[ext]/[name].[hash]${Timestamp}.[ext]`,//配置静态资源文件的输出文件名规则。
        }
      }
    },
    // 配置 CSS
    css: {
      postcss: {
        plugins: [
          // 处理 @import 和 url() 路径
          postcssImport(),
          postcssUrl(),
          
          // 自动添加浏览器前缀
          autoprefixer({
            overrideBrowserslist: [
              'last 5 version',
              '>1%',
              'ie >=8'
            ]
          }),
          
          // px 转 rem(核心适配插件)
          pxtorem({
            rootValue: 192, // 1920设计稿除以10
            minPixelValue: 1, // 最小转换值,1px及以上转换
            unitPrecision: 6, // 转换后的小数位数
            propList: ['*'], // 所有属性都转换 大写PX不会被转换 
            selectorBlackList: ['aaa-'], // 匹配不被转换的选择器
            replace: true, // 直接替换而不是添加备用
            mediaQuery: false, // 媒体查询中的px不转换 
            exclude: /node_modules/i // 排除 node_modules
          })
        ]
      }
    },
    
  }
})

c.配置打包命令以及打包

 “build:prod”: “vite build –mode prod”,

这里就会根据env.prod配置文件进行打包

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

注意: 在React 18中, StrictMode 会在开发环境下对组件进行两次渲染以检测潜在问题,这导致 useEffect 钩子被执行两次,从而产生了两次网络请求。这种行为是React 18引入的新特性,只在开发环境下发生,生产环境不会有。

五、实现 Ant Design 的按需加载

Ant Design 5.x或者以上默认支持Tree Shaking,默认支持按需加载

这里需要定制主题或者样式兼容,你可以使用 
@ant-design/cssinjs
 取消默认的降权操作(请注意版本保持与 antd 一致)

在 Vite + React + Ant Design 中使用 CSS-in-JS(ant5.x.x及以上)

https://ant.design/docs/react/compatible-style-cn

npm install antd @ant-design/icons

# CSS-in-JS 相关
npm install @ant-design/cssinjs



//main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { StyleProvider } from '@ant-design/cssinjs';
import {ConfigProvider} from "antd";
import 'amfe-flexible'; // 引入自适应方案
import './index.css'
import '@/assets/css/base.css'
import App from './App.jsx'
// Redux
import { Provider } from 'react-redux'
import { store } from './store/index.js'
 
 
// 1. 恢复StrictMode :重新启用了React的严格模式,这有助于发现潜在问题
// 2. 移除重复代码 :删除了注释掉的重复 <App /> 组件
// 3. 优化缩进 :调整了代码缩进,使组件层级结构更清晰
// 当前的配置结构是正确的,遵循了Ant Design 6.x的最佳实践:
 
// - 使用 StyleProvider 来自定义样式优先级和哈希策略
// - 使用 ConfigProvider 来全局配置Ant Design组件
// - 使用 Provider 来提供Redux store
 
createRoot(document.getElementById('root')).render(
  <StrictMode>
    <StyleProvider hashPriority="high">
      <ConfigProvider>
        <Provider store={store}>
          <App />
        </Provider>
      </ConfigProvider>
    </StyleProvider>
  </StrictMode>
)

修改ant组件样式使用:global()修饰符来确保样式能够正确应用到Ant组件内部的类名上,因为在CSS Modules中默认样式是作用域化

基于Vite快速建立react项目,以及实现自适应适配和axios的封装

六、Vite旧版浏览器兼容插件指南: @vitejs/plugin-legacy

这是一个 Vite 插件,用于为旧版本的浏览器提供兼容性支持。它的主要作用是将现代 JavaScript 代码(例如,ES6+、async/await、模块化等)转换成较旧版本浏览器(如 IE11 或早期版本的 Safari、Chrome)能够理解和运行的代码。该插件的工作原理基于 Babel 和 Polyfill。

注意:

– 1. @vitejs/plugin-legacy 对应vite版本要求

在项目中package.json中查看 vite版本,一般只需要找对应的大版本就行;【eg: vite ^7 ==> @vitejs/plugin-legacy ^7】

-2. 使用 @vitejs/plugin-legacy 打包后,包体积增大

插件会自动为旧版本浏览器引入一些 Polyfill(例如 core-js 和 regenerator-runtime)。这些 Polyfill 是为了确保现代 JavaScript 特性能够在旧浏览器中正常工作。由于 Polyfill 涉及到许多额外的库和代码,它会增加最终打包文件的体积。

a.安装依赖

– 查看可用版本

npm view @vitejs/plugin-legacy versions –json

– 下载对应的版本xx

npm i @vitejs/plugin-legacy@x.x.x -D 

– 必须安装 Terser,因为 plugin-legacy 使用 Terser 进行缩小

npm add -D terser

b.项目配置



//vite.config.js
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import postcssImport from 'postcss-import';
import postcssUrl from 'postcss-url';
import autoprefixer from 'autoprefixer';
import pxtorem from 'postcss-pxtorem';
import legacy from '@vitejs/plugin-legacy'
 
const Timestamp = new Date().getTime();//随机时间戳
// https://vite.dev/config/
export default defineConfig(({mode})=>{
  const env = loadEnv(mode, process.cwd());
  console.log(env);
  let fileName = 'dist'
	// 兼容性,以防打包崩溃
	fileName = env.VITE_APP_NAME
  return {
    base: '',//项目名称开发或生产环境服务的公共基础路径。
    plugins: [react(),
        legacy({
      // 设置需要兼容的目标浏览器版本
          targets: ['ie >= 11', '> 1%', 'last 2 versions','chrome > 68'],
          // 为传统包提供 polyfills
          polyfills: ['es.promise.finally', 'es/map', 'es/set'],
          modernPolyfills: false,
          renderLegacyChunks: true,
          // 在使用协议本地运行项目时file:,这也很有帮助,因为加载现代代码块type="module"可能会触发 CORS 限制。
          // 为了避免此问题,只需设置renderModernChunks为false仅使用旧代码块即可
          renderModernChunks: false,
          // 如果你使用 `regenerator-runtime` (例如用了 async/await), 需要启用它
          additionalLegacyPolyfills: ['regenerator-runtime/runtime']
        })
    ],
    resolve:{
      alias:{
        '@': path.resolve(__dirname, './src')
      }
    },
    server:{
          proxy: {
              '/DreamOne': {
                  target: 'http://10.3.4.174:8080/DreamOne/',
                  changeOrigin: true,
                  rewrite: (path) =>  {
                      return  path.replace(/^/DreamOne/, '')
                  },
              }
          }
    },
     build: {
      outDir: fileName, // 打包后文件包名称
      sourcemap: false,//控制是否生成源映射文件
      target: ['ios11', 'es2015'],//指定目标浏览器和 JavaScript 版本。解释:这里使用了 ios11 和 es2015,表示你希望构建的代码在 iOS 11 及更高版本的浏览器中运行,并且使用 ES2015 的语法。这有助于优化构建输出,使其更适应目标环境。
      rollupOptions: {
        // 使用了模板字符串和 ${Timestamp} 变量,这是为了在文件名中添加构建时的时间戳,以避免浏览器缓存问题。这样每次构建都会生成带有新时间戳的文件名,确保文件更新后不受缓存影响
        output: {
          chunkFileNames: `static/js/[name].[hash]${Timestamp}.js`,//配置代码拆分后的 chunk 文件名规则。
          entryFileNames: `static/js/[name].[hash]${Timestamp}.js`,//配置入口文件的输出文件名规则。
          assetFileNames: `static/[ext]/[name].[hash]${Timestamp}.[ext]`,//配置静态资源文件的输出文件名规则。
        }
      }
    },
    // 配置 CSS
    css: {
      postcss: {
        plugins: [
          // 处理 @import 和 url() 路径
          postcssImport(),
          postcssUrl(),
          
          // 自动添加浏览器前缀
          autoprefixer({
            overrideBrowserslist: [
              'last 5 version',
              '>1%',
              'ie >=8'
            ]
          }),
          
          // px 转 rem(核心适配插件)
          pxtorem({
            rootValue: 192, // 1920设计稿除以10
            minPixelValue: 1, // 最小转换值,1px及以上转换
            unitPrecision: 6, // 转换后的小数位数
            propList: ['*'], // 所有属性都转换 大写PX不会被转换 
            selectorBlackList: ['aaa-'], // 匹配不被转换的选择器
            replace: true, // 直接替换而不是添加备用
            mediaQuery: false, // 媒体查询中的px不转换 
            exclude: /node_modules/i // 排除 node_modules
          })
        ]
      }
    },
    // 设置构建目标,legacy 插件会为传统浏览器生成相应的包
    target: 'es2015', // 或 ‘es5’
    minify: true, // 代码压缩
  }
})

这里有优化压缩体积的方式:https://www.jb51.net/article/271663.htm

vite官方文档:开始 {#getting-started} | Vite中文网 (vitejs.cn)

redux安装配置:Redux 基础,第二节:应用的结构 | Redux 中文官网

一个统计前端技术走向的网站:2021 JavaScript Rising Stars

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...