企业级前端工程化配置指南:vite + react + redux + react-router + umi-request + antd + 编码规范

其实,我只在四年前完整的自己搭建过react项目,后来都基于umijs一键一把梭了。最近闲得慌,再加上最近一两年react项目开发的少,知识点感觉跟不上了,就想着复盘一下,随从项目搭建开始...

你可以学到什么?

  • 如何使用 vite 搭建项目

  • 如何集成与使用 web-localstorage-plus

  • 如何集成与使用 react-router6

  • 如何集成与使用 redux

  • 如何集成与使用 ant-design

  • 如何封装与使用 umi-request

  • 如何借力 eslint 和 prettier 保证代码质量

  • 如何借力 commitlint 规范git提交信息

1.按提示创建项目

  • 运行vite
yarn create vite
  • 输入自定义的项目名称
name: › your-project-name
  • 选择你想要的技术框架
? Select a framework: › - Use arrow-keys. Return to submit.
    Vanilla
    Vue
❯  React
    Preact
    Lit
    Svelte
    Others
  • 选择ts模板
? Select a variant: › - Use arrow-keys. Return to submit.
    TypeScript
❯  TypeScript + SWC
    JavaScript
    JavaScript + SWC

5.按提示安装并运行项目

Done. Now run:
  cd react-blob
  yarn
  yarn dev

6.优化项目结构

打开工程可以看到,vite默认了部分页面和配置,大多数其实都是无用的,需要进行下修剪。此处省略过程,有需要的可以(拉取源码)[https://github.com/supanpanCn/svite]查看

2.配置vite.config.ts

这里和vite+vue工程化配置是一样的,此处不再赘述,感兴趣的可直接跳转查看

3.集成web-storage-plus

对于需要使用到持久缓存的地方,localstorage是优选的方案,不过原生接口比较难用,而该npm包对其进行了二次封装,使其支持了命名空间、过期时间、监听变化、批量操作等特性,笔者的项目里一致都在用,文档看这里:传送门

  • 安装
yarn add web-localstorage-plus@next
  • main.tsx中引入并初始化根存储
import createStorage from 'web-localstorage-plus'

createStorage({
    // 根命名空间
    rootName:'spp-storage',
    // 是否禁用原生localstorage
    noUseLocalStorage:false
})

  • 在xxx.tsx文件中引入并使用
import { useStorage } from "web-localstorage-plus";

function Storage() {
  const storage = useStorage();
  storage.setItem("name", "spp", "author");
  storage.setItem("age", 29, "author");
  return <>learn storage</>;
}

export default Storage;
  • 存储结果如下
image.png

4.集成react-router

  • 安装依赖
yarn add react-router-dom
  • 配置路由表

在src目录下新建router文件夹,并在router下新建index.ts文件,内容如下

import { createHashRouter } from "react-router-dom";
import User from "../pages/user";

const router = createHashRouter([
  {
    path: "/",
  },
  {
    path: "/user",
    Component: User,
  },
]);

export default router;
  • 路由出口

找到App.tsx文件,引入并注入路由表

import Storage from './pages/storage';
import { RouterProvider } from 'react-router-dom';
import router from './router'

function App() {
  return (
    <>
      <RouterProvider router={router}/>
      <br />
      <Storage/>
    </>
  )
}

export default App
  • 预览
Dec-09-2023 14-46-13.gif

5.集成redux

  • 安装redux
yarn add @reduxjs/toolkit react-redux
  • 创建根store

在根目录下新建store/index.ts文件

import { configureStore } from '@reduxjs/toolkit'

export default configureStore({
  reducer: {
  }
})

  • 拆分reducer

store下新建xxx.ts,笔者这里为calculate.ts

import { createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
  name: 'calculate',
  initialState: {
    value: 0
  },
  reducers: {
    increment: state => {
      state.value += 1
    },
  }
})

export const { increment } = counterSlice.actions
export default counterSlice.reducer

将定义的calculate.ts导入到index.ts中并设置

import calculateReducer from './calculate'

export default configureStore({
  reducer: {
    calculate:calculateReducer
  }
})
export { increment } from './calculate'
  • 全局注入

main.ts中注入store

import store from "./store";
import { Provider } from "react-redux";

ReactDOM.createRoot(...).render(
  ...
    <Provider store={store}>
      ...
    </Provider>
  ...
);
  • xxx.tsx组件页面中使用

可以看到,报了TypeScript相关的类型错误

image.png

这需要返回store/index.ts中导出类型

export type Store = ReturnType<typeof store.getState>

并在xxx.tsx组件内导入类型并作为泛型传入

import { ...,Store } from "../store";
... useSelector<Store>(...);
  • 预览
redux.gif
  • 支持异步

一般使用第三方中间件redux-sagaredux-thunk,不过笔者从工作开始,就没在状态程序中写过异步,感觉不甚重要,此处就不引入了

6.集成antd

  • 安装
yarn add antd
  • 使用
import { DatePicker } from 'antd'
function Atd() {
  return <>
    <DatePicker/>
  </>;
}
export default Atd;
  • 预览
image.png
  • 全局配置

预览中可以看到,默认语言是英文

找到app.ts文件,修改如下

image.png

再次预览,就变成中文了

image.png

7.集成umi-request

  • 安装
yarn add umi-request
  • 配置

在根目录下新建api文件夹,在api下新建index.ts文件,此处统一进行配置。其实和axios一样,都是设置下拦截器统一处理请求的发起和接收

一般来说,在请求发起时拦截,是为了统一增加字段,比如token。又或者,改变url前缀,毕竟同一个项目可能需要向不同的后端服务发起请求;在响应接收阶段,则是统一输出,以使业务代码接收统一

import { extend, type ResponseError } from "umi-request";
import { stringify } from "qs";
import { message } from "antd";

const errorHandler = (
  err: ResponseError & {
    description?: string;
  }
) => {
  message.destroy();
  message.error(err.description || "接口请求失败,请稍后再试...");
  return {
    code: -1,
  };
};

// 拦截错误
const Request = extend({
  timeout: 30000,
  errorHandler,
});

// 拦截请求
Request.interceptors.request.use((url) => {
  return {
    url,
  };
});

// 拦截响应

Request.interceptors.response.use((response) => {
  return new Promise(function (resolve, reject) {
    response.text().then((res) => {
      let resData;
      try {
        resData = JSON.parse(res);
      } catch (err) {
        resData = { code: -1 };
      }
      if (resData.responseCode === 200) {
        resolve(resData);
      } else {
        reject(resData);
      }
    });
  });
});

export default Request;

// 序列化函数--辅助用

export function stringifyWithTrim(params = {}) {

  function encoder(str:unknown, defaultEncoder:(str: unknown, defaultEncoder?: unknown, charset?: string) => string) {
    if (typeof str === "string") {
      return defaultEncoder(str.trim());
    }
    return defaultEncoder(str);
  }
  return stringify(params, { encoder });
}

  • 使用

api文件夹下按模块新建并引入index.ts中的导出模块进行接口请求即可,一般对于get请求还需要对参数进行序列化

import request,{stringifyWithTrim} from "./index.ts";

export default {
    post(params){
      return request(url, {
        method: "POST",
        params,
      })
    },
    get(query){
      return request(`url?${stringifyWithTrim(query)}`)
    }
}

8.css隔离

样式方案有很多,你也可以使用lessscss等预处理器。笔者这里以模块化方式举例

  • css模块

在根目录下创建style文件夹,并新建xxx.module.css

.wrapper {
  color: red;
}

  • 使用

首先,安装classnames

yarn add classnames

接着导入并使用

import classnames from "classnames";
import CSS from "../style/css.module.css";

function Css() {
  const klass = classnames({
    [CSS.wrapper]: true,
  });
  return <div className={klass}>css</div>;
}

export default Css;

  • 预览
image.png

9.代码质量与提交规范

这里的完整步骤可以参考vite+vue工程化配置

首先,安装相关依赖


yarn add lint-staged husky @commitlint/config-conventional @commitlint/cli -D

然后,修改package.json


"scripts": {

    "prepare": "husky install",

    "check": "lint-staged"

},

"lint-staged": {

    "*.{tsx,ts}": [

      "npm run lint"

    ]

}

接着,注册husky相关钩子


npx husky add .husky/pre-commit "npm run check"

git add .husky/pre-commit

npx husky add .husky/commit-msg  'npx --no -- commitlint --edit ${1}'

再然后,在根目录下定义commitlint.config.cjs配置文件

module.exports = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "feature", // 迭代功能
        "conf", // 修改构建配置
        "fixbug", // 修复bug
        "refactor", // 代码重构
        "optimize", // 代码优化
        "style", // 仅修改样式文件
        "docs", // 文档补充说明
      ],
    ],
    "header-max-length": [0, "always", 72], //限制最长72
  },
};

最后,测试下。可以成功拦截

image.png

源码地址

传送门

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容