头图

其实,我只在四年前完整的自己搭建过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;
  • 存储结果如下

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
  • 预览

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相关的类型错误

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

export type Store = ReturnType<typeof store.getState>

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

import { ...,Store } from "../store";

... useSelector<Store>(...);
  • 预览

  • 支持异步

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

6.集成antd

  • 安装
yarn add antd
  • 使用
import { DatePicker } from 'antd'

function Atd() {
  return <>
    <DatePicker/>
  </>;
}

export default Atd;
  • 预览

  • 全局配置

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

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

再次预览,就变成中文了

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;
  • 预览

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
  },
};

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

源码地址

传送门


Sir_苏
4 声望2 粉丝