项目地址
环境准备
兼容性注意:Vite 需要 Node.js 版本 >= 12.0.0。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
我的 node 版本是 v12.13.0
,npm 版本是 6.12.0
。
1、初始化 Vite + React 项目
直接用官方提供的模板,一键生成项目:项目命名为 kaimo-cost-h5
# npm 6.x
npm init @vitejs/app kaimo-cost-h5 --template react
然后进入项目安装依赖,就可以启动服务了
cd kaimo-cost-h5
npm install
npm run dev
2、添加 react-router-dom 路由
安装依赖
执行下面命令安装依赖
npm i react-router-dom -S
添加页面组件
在项目 src 目录下新增 container
目录用于放置页面组件,再在 container
下新增两个目录分别是 Index
和 About
。
About/index.jsx
添加如下内容:
import React from 'react'
export default function Index() {
return <div>
kaimo 的 about 页面
</div>
}
Index/index.jsx
添加如下内容:
import React from 'react'
export default function Index() {
return <div>
kaimo 的 index 页面
</div>
}
添加路由数组
新建 src/router/index.js
配置路由数组,添加如下内容:
import Index from '../container/Index'
import About from '../container/About'
const routes = [
{
path: "/",
component: Index
},
{
path: "/about",
component: About
}
];
export default routes
引入路由配置
修改 App.jsx
里的代码,引入路由配置,实现切换浏览器路径,显示相应的组件:
import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import routes from '../src/router'
function App() {
return <BrowserRouter>
<Routes>
{
routes.map(route => <Route key={route.path} path={route.path} element={<route.component />}></Route>)
}
</Routes>
</BrowserRouter>
}
export default App
这里记得包裹一层 BrowserRouter
,不然会报错 useRoutes() may be used only in the context of a <Router> component.
我们测试一下:
首先我们访问:http://localhost:3000/
,没有问题
然后访问:http://localhost:3000/about
,也没问题
3、添加 Zarm UI 组件库
安装依赖
npm install zarm -S
使用
下面我们在 App.jsx
里添加代码:
import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { ConfigProvider } from 'zarm';
import zhCN from 'zarm/lib/config-provider/locale/zh_CN';
import 'zarm/dist/zarm.css';
import routes from '../src/router'
function App() {
return <BrowserRouter>
<ConfigProvider locale={zhCN}>
<Routes>
{
routes.map(route => <Route key={route.path} path={route.path} element={<route.component />}></Route>)
}
</Routes>
</ConfigProvider>
</BrowserRouter>
}
export default App
然后在 /container/Index/index.jsx
里添加按钮 Button 组件:
import React from 'react'
import { Button } from 'zarm';
export default function Index() {
return <div>
kaimo 的 index 页面
<h3>
按钮 Button 基本用法
</h3>
<Button>default</Button>
<Button theme="primary">primary</Button>
</div>
}
按需引入
我们执行 npm run build
打包时,发现 css 文件过于太大,有必要进行按需引入。
我们使用插件vite-plugin-style-import:按需导入组件库样式
注意:这里我们用 1.0.1
的版本,我试了一下 2.0.0
的发现报错。
npm i vite-plugin-style-import@1.0.1 -D
安装好之后,我们修改 vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import styleImport from 'vite-plugin-style-import'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
styleImport({
libs: [
{
libraryName: 'zarm',
esModule: true,
resolveStyle: (name) => {
return `zarm/es/${name}/style/css`;
}
}
]
})
]
})
再修改 App.jsx
里,去掉样式全局引入
import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { ConfigProvider } from 'zarm';
import routes from '../src/router'
function App() {
return <BrowserRouter>
<ConfigProvider>
<Routes>
{
routes.map(route => <Route key={route.path} path={route.path} element={<route.component />}></Route>)
}
</Routes>
</ConfigProvider>
</BrowserRouter>
}
export default App
然后我们在打包 npm run build
,我们发现 css 打包后的体积少了很多。
4、配置 CSS 预处理器 Less
安装依赖
npm i less -D
配置 css 属性
更多请参考:postcss-modules
导出类名的样式,json 中的键。
Name | Type | Description |
---|---|---|
camelCase | String | 类名将被骆驼化,原始类名不会从本地中删除 |
camelCaseOnly | String | 类名将被驼峰化,原来的类名将从本地中删除 |
dashes | String | 只有类名中的破折号会被驼峰化 |
dashesOnly | String | 类名中的破折号将被驼峰化,原始类名将从本地中删除 |
在 vite.config.js
里面添加配置:这里我们用 dashesOnly
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import styleImport from 'vite-plugin-style-import'
// https://vitejs.dev/config/
export default defineConfig({
css: {
// css模块化,文件以.module.[css|less|scss]结尾,否则不生效的
modules: {
/**
* 配置 CSS modules 的行为。选项将被传递给 postcss-modules。
* 默认:'camelCaseOnly'。
* 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly'
* */
localsConvention: 'dashesOnly'
},
// 指定传递给 CSS 预处理器的选项。
preprocessorOptions: {
// 预编译支持 less
less: {
// 支持内联 JavaScript
javascriptEnabled: true,
}
}
},
plugins: [
react(),
styleImport({
libs: [
{
libraryName: 'zarm',
esModule: true,
resolveStyle: (name) => {
return `zarm/es/${name}/style/css`;
}
}
]
})
]
})
新建 container/Index/style.module.less
文件,里面添加代码:
.kaimo-index {
h3 {
color: green;
}
}
.kaimo_index {
h3 {
color: red;
}
}
然后在 container/Index/index.jsx
里面引入使用
import React from 'react'
import { Button } from 'zarm';
import k from './style.module.less'
console.log('style.module.less', k)
export default function Index() {
return <div className={k.kaimoIndex}>
kaimo 的 index 页面
<h3>
按钮 Button 基本用法
</h3>
<Button>default</Button>
<Button theme="primary">primary</Button>
</div>
}
我们可以看到在 dashesOnly
下,类名中的破折号被驼峰化了。并且添加了 css modules 配置,自定义样式重名的风险也避免了。
我们可以修改一下在 camelCaseOnly
下,类名中都被驼峰化了。并且被覆盖了,样式变红了。
5、移动端项目适配 rem
安装依赖
postcss-pxtorem
:它的作用是在你编写完 css 后,将你的单位自动转化为 rem 单位。lib-flexible
:可伸缩布局方案。
npm i postcss-pxtorem lib-flexible -s
使用 flexible
在 main.jsx
中引入它:
import React from 'react'
import ReactDOM from 'react-dom'
import 'lib-flexible/flexible'
import './index.css'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
引入之后就会出现下面的 font-size
的设置,页面的大小变化就会让 html 的 font-size
随之变化,从而形成可伸缩布局。
配置 postcss-pxtorem
更多配置参考:postcss-pxtorem
一些默认配置:
{
rootValue: 16, // (Number | Function) 表示根元素字体大小或根据输入参数返回根元素字体大小
unitPrecision: 5, // (Number) 允许 REM 单位增长到的十进制数字。
propList: ['font', 'font-size', 'line-height', 'letter-spacing'], // (Array)可以从 px 更改为 rem 的属性。
selectorBlackList: [], // (Array)要忽略并保留为 px 的选择器。
...
}
根据文档,我们先在根目录新建一个 postcss.config.js
文件,里面添加配置信息:
/**
* 具体配置可以去 postcss-pxtorem 仓库看看文档
* 默认配置
rootValue: 16,
unitPrecision: 5,
propList: ['font', 'font-size', 'line-height', 'letter-spacing'],
selectorBlackList: [],
replace: true,
mediaQuery: false,
minPixelValue: 0,
exclude: /node_modules/i
* */
module.exports = {
"plugins": [
require("postcss-pxtorem")({
rootValue: 37.5,
propList: ['*'],
selectorBlackList: ['norem'] // 过滤掉 .norem 开头的 class,不进行 rem 转换,比如:['body'] will match .body-class
})
]
}
我们来测试一下:
先修改 container/Index/style.module.less
文件,里面添加代码:
.kaimo-index {
h3 {
width: 100px;
background-color: pink;
color: green;
}
.norem-kaimo {
width: 200px;
}
}
然后修改 container/Index/index.jsx
里面的代码:
import React from 'react'
import { Button } from 'zarm';
import k from './style.module.less'
console.log('style.module.less', k)
export default function Index() {
return <div className={k.kaimoIndex}>
kaimo 的 index 页面
<h3>
按钮 Button 基本用法
</h3>
<Button>default</Button>
<Button theme="primary">primary</Button>
<h3 className={k.noremKaimo}>
按钮 Button 基本用法 norem
</h3>
</div>
}
重启项目 npm run dev
,我们可以发现加了 norem
前缀的类名的 px 不会转化为 rem。
6、resolve.alias 别名设置
打开 vite.config.js
,添加配置如下:
import path from "path";
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'), // src 路径
}
},
}
配置好之后,我们就可以使用:
import Index from '@/container/Index'
import About from '@/container/About'
const routes = [
{
path: "/",
component: Index
},
{
path: "/about",
component: About
}
];
export default routes
7、进行 axios 封装配置
安装依赖
npm i axios -S
知识点
环境变量
Vite 在一个特殊的 import.meta.env
对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量:
import.meta.env.MODE
:{string}
应用运行的模式。import.meta.env.BASE_URL
:{string}
部署应用时的基本 URL。他由 base 配置项决定。import.meta.env.PROD
:{boolean}
应用是否运行在生产环境。import.meta.env.DEV
:{boolean}
应用是否运行在开发环境 (永远与import.meta.env.PROD
相反)。
X-Requested-With
X-Requested-With
用于区分 ajax 请求还是传统请求。如果requestedWith
为 null,则为同步请求。如果requestedWith
为XMLHttpRequest
则为 Ajax 请求。
XMLHttpRequest.withCredentials
XMLHttpRequest.withCredentials
属性是一个Boolean
类型,它指示了是否该使用类似cookies,authorization headers
(头部授权)或者TLS
客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control
)请求。在同一个站点下使用withCredentials
属性是无效的。
代理配置
我们启动之前的后端项目 kaimo-cost-server
,地址是:http://127.0.0.1:7001
。
然后我们打开 vite.config.js
,添加代理配置,代码如下:
export default defineConfig({
server: {
proxy: {
'/api': {
// 当遇到 /api 路径时,将其转换成 target 的值
target: 'http://127.0.0.1:7001',
changeOrigin: true,
}
}
},
})
封装处理
下面我们新建 src/utils/axios.js
文件,里面添加代码:
import axios from "axios";
// 引入 zarm 的 Toast 组件用于提示
import { Toast } from "zarm";
// 设置接口响应时间 30s
axios.defaults.timeout = 3000;
/**
* @description 添加请求拦截器
* 1. config中的一些信息不符合服务器的要求,这里可以做一些修改
* 2. 每次发送网络请求时,都希望在界面中显示一个请求的图标(然后再响应拦截中取消显示)
* 3. 某些网络请求必须携带一些特殊的信息(如登录token),如果没有携带就可以拦截并作响应提示
*/
axios.interceptors.request.use(config => {
// 看知识点 XMLHttpRequest.withCredentials
config.withCredentials = true;
// 看知识点 X-Requested-With
config.headers['X-Requested-With'] = 'XMLHttpRequest';
// Authorization 用于服务端鉴权,token 存于 localStorage
config.headers['Authorization'] = `${localStorage.getItem('token') || null}`;
console.log('进入请求拦截器-->config:', config);
return config;
}, err => {
// 请求未成功发出,如:没有网络...
console.log('进入请求拦截器-->err:', err);
Toast.show("请求失败");
return Promise.reject(err);
})
// 添加响应拦截器
axios.interceptors.response.use(response => {
// 成功响应的拦截
console.log('进入响应拦截器-->response:', response);
return Promise.resolve(response.data)
}, err =>{
// 失败响应的拦截
console.log('进入响应拦截器-->err:', err);
Toast.show("服务器响应失败");
if(err.response){
// 失败响应的status需要在response中获得
console.log(err.response)
switch(err.response.status){
// 对得到的状态码的处理,具体的设置视自己的情况而定
case 401:
console.log('未登录')
window.location.href='/login';
break
case 404:
console.log('没有找到该方法');
break
case 405:
console.log('不支持的方法');
break
default:
console.log('其他错误');
break
}
}
// 注意这里应该return promise.reject(),
// 因为如果直接return err则在调用此实例时,响应失败了也会进入then(res=>{})而不是reject或catch方法
return Promise.reject(err)
});
/**
* @description 导出获取数据方法
* @param {String} url
* @param {String} type
* @param {Object} data
* */
export function fetchData(url, type, data) {
let ajaxType = type ? type.toLowerCase() : type;
const config = {};
config.method = ajaxType || 'post';
config.url = url;
config.headers = {};
if (ajaxType === 'get') {
config.url = url + '?r=' + Math.random() * 1000;
config.params = data;
} else if (ajaxType === 'post') {
config.data = data;
config.headers['Content-Type'] = 'application/json;charset=UTF-8';
}
console.log('进入fetchData', url, type, data, config);
return axios(config);
};
测试一下:我们在 Index/api
该路径添加 index.js
文件,该文件里面添加接口的配置代码:
import { fetchData } from "@/utils/axios.js";
export function login(data) {
return fetchData('/api/user/login', 'post', data);
}
然后在文件 Index/index.jsx
里添加登录按钮的代码
import React from 'react'
import { Button, Toast } from 'zarm';
import k from './style.module.less'
console.log('style.module.less', k)
import { login } from "./api/index.js";
const onSubmit = async () => {
try {
const data = await login({
username: "kaimo313",
password: "123456"
});
console.log(data);
Toast.show('登录成功');
} catch (error) {
console.log(error)
Toast.show('系统错误');
}
};
export default function Index() {
return <div className={k.kaimoIndex}>
kaimo 的 index 页面
<h3>
按钮 Button 基本用法
</h3>
<Button>default</Button>
<Button theme="primary">primary</Button>
<h3 className={k.noremKaimo}>
按钮 Button 基本用法 norem
</h3>
<Button onClick={onSubmit} block theme="primary">登录</Button>
</div>
}
点击登录,我们发现可以登录成功
这样,我们的基本环境就搭建好了。
package.json
{
"name": "kaimo-cost-h5",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^0.26.1",
"lib-flexible": "^0.3.2",
"postcss-pxtorem": "^6.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.3.0",
"zarm": "^2.9.13"
},
"devDependencies": {
"@vitejs/plugin-react": "^1.0.7",
"less": "^4.1.2",
"vite": "^2.9.0",
"vite-plugin-style-import": "^1.0.1"
}
}
vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from "path";
import styleImport from 'vite-plugin-style-import'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'), // src 路径
}
},
css: {
// css模块化,文件以.module.[css|less|scss]结尾,否则不生效的
modules: {
/**
* 配置 CSS modules 的行为。选项将被传递给 postcss-modules。
* 默认:'camelCaseOnly'。
* 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly'
* */
localsConvention: 'dashesOnly'
},
// 指定传递给 CSS 预处理器的选项。
preprocessorOptions: {
// 预编译支持 less
less: {
// 支持内联 JavaScript
javascriptEnabled: true,
}
}
},
server: {
proxy: {
'/api': {
// 当遇到 /api 路径时,将其转换成 target 的值
target: 'http://127.0.0.1:7001',
changeOrigin: true,
}
}
},
plugins: [
react(),
styleImport({
libs: [
{
libraryName: 'zarm',
esModule: true,
resolveStyle: (name) => {
return `zarm/es/${name}/style/css`;
}
}
]
})
]
})
参考资料
- Vite 官方中文文档
- 【蜻蜓点水】了解一下 vite 下一代前端开发与构建工具
- vite-plugin-style-import:按需导入组件库样式
- React 官方中文文档 – 用于构建用户界面的 JavaScript 库
- React Router v6
- React Router v5
- react-router-dom v6 使用
- React-Router v6 新特性解读及迁移指南
- 使用 sass + rem + flexible.js 实现大屏自适应
- Less 快速入门 | Less.js 中文文档 - Less 中文网
- postcss-modules
- postcss-pxtorem
- axios中文文档|axios中文网
- XMLHttpRequest.withCredentials
- x-requested-with的作用以及用法详解
- Zarm Design:追求极致的用户体验,做有温度的组件库