Skip to content
霞露小伙 — HfWang
On this page

使用 vite 快速创建

  1. 快速创建

    pnpm create vite --template react-ts
  2. 安装依赖

    pnpm i
    
    pnpm add react-router-dom mobx mobx-react-lite ahooks @arco-design/web-react
    
    pnpm add lodash-es @types/lodash-es axios qs @types/qs @types/node @icon-park/react vite-plugin-windicss windicss less @arco-plugins/vite-react nprogress @types/nprogress -D

    安装后的 packages.json

    json
    {
        "name": "vite-project",
        "private": true,
        "version": "0.0.0",
        "type": "module",
        "scripts": {
            "dev": "vite",
            "build": "tsc && vite build",
            "preview": "vite preview"
        },
        "dependencies": {
            "@arco-design/web-react": "^2.40.0",
            "ahooks": "^3.7.1",
            "mobx": "^6.6.2",
            "mobx-react-lite": "^3.4.0",
            "react": "^18.2.0",
            "react-dom": "^18.2.0",
            "react-router-dom": "^6.4.1"
        },
        "devDependencies": {
            "@arco-plugins/vite-react": "^1.3.1",
            "@icon-park/react": "^1.4.2",
            "@types/lodash-es": "^4.17.6",
            "@types/node": "^18.7.18",
            "@types/nprogress": "^0.2.0",
            "@types/qs": "^6.9.7",
            "@types/react": "^18.0.17",
            "@types/react-dom": "^18.0.6",
            "@vitejs/plugin-react": "^2.1.0",
            "axios": "^0.27.2",
            "less": "^4.1.3",
            "lodash-es": "^4.17.21",
            "nprogress": "^0.2.0",
            "qs": "^6.11.0",
            "typescript": "^4.6.4",
            "vite": "^3.1.0",
            "vite-plugin-windicss": "^1.8.8",
            "windicss": "^3.5.6"
        }
    }

初始化

修改目录

shell
packages.json
index.html
tsconfig.json
tsconfig.node.json
vite.config.ts     // 项目配置
.gitignore         // git 忽略文件
.env               // 环境变量
public             // 静态文件
src
	assets         // 静态文件
	layouts        // 布局文件
	pages          // 页面文件
	service        // 接口服务文件
	utils          // 工具文件
	router         // 路由文件
	store          // 全局状态文件
	App.tsx        // 项目跟组件
   	main.tsx       // 项目入口文件
   	vite-env.d.ts  // 全局的类型声明文件

配置别名和代理

ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { resolve } from "path";

export default defineConfig({
    plugins: [react()],
    resolve: {
        alias: {
            // 注意这里配置的别名需要再 tsconfig 的 paths 属性下声明
            "@": resolve(__dirname, "src"),
        },
        // 导入时可以省略的扩展名列表
        extensions: ["mjs", ".js", "ts", "jsx", "tsx"],
    },
    server: {
        port: 3033,
        proxy: {
            "/api": {
                changeOrigin: true,
                target: "https://www.xxxx.com",
                rewrite: (url) => url.replace("/api", ""),
            },
        },
    },
});
json
// tsconfig.json
{
    "compilerOptions": {
        // .....
        "paths": {
            "@/*": ["./src/*"]
        }
    }
}

WindiCss

具体配置:windicss-vite

ts
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import WindiCss from "vite-plugin-windicss";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        react(),
        WindiCss(), // 添加 windicss 插件
    ],
});
ts
// main.ts
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "virtual:windi.css"; // 添加 windicss

const app = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
app.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);

使用

tsx
const demo = () => <div className="mr-4">demo</div>;

IconPark

tsx
// main.ts
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "virtual:windi.css"; // 添加 windicss
import "@icon-park/react/styles/index.css"; // 添加 iconpark 样式

const app = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
app.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);

使用

tsx
import { CheckOne } from "@icon-park/react";
// iconpark 可以直接复制出对应的 react 代码
const demo = () => <CheckOne theme="filled" size="32" fill="#666"></CheckOne>;

Arco 组件库

具体配置:

@arco-plugins/vite-react

acro.design

ts
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import WindiCss from "vite-plugin-windicss";
import vitePluginForArco from "@arco-plugins/vite-react";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        react(),
        WindiCss(),
        vitePluginForArco({
            style: true, // 动态导入组件样式 less
        }),
    ],
});

使用

tsx
import { Button } from "@arco-design/web-react";
const demo = () => <Button type="primary">我是按钮</Button>;

路由

全局状态

具体使用:

Mobx6 集成 React 和 Typescript 实践应用

【注意】: mobx-react-lite 只支持函数组件

定义 store

ts
// store/user.ts
import { action, computed, makeAutoObservable, makeObservable, observable } from "mobx";

export interface UserStoreType {
    name: string;
    age: number;
    doubleAge: number;
    editName: (name: string) => void;
}

class UserStore implements UserStoreType {
    name: string = "";
    age: number = 0;
    constructor() {
        // makeObservable需要手动注解
        makeObservable(this, {
            name: observable,
            age: observable,
            doubleAge: computed,
            editName: action,
        });
    }

    get doubleAge(): number {
        return this.age * 2;
    }

    editName = (name: string) => {
        this.name = name;
    };
}

export { UserStore };
ts
// store/index.ts
import { createContext } from "react";
import { UserStore, UserStoreType } from "./user";

export type { UserStoreType };

export const stores = {
    userStore: new UserStore(),
};

export const useStore = useContext(createContext(stores));

使用 store

tsx
// demo.tsx
import { store } from "./store";
import { observer } from "mobx-react-lite";

const demo = () => <div>{store.userStore.name}</div>;

// 重点
export default observer(demo);

Axios

ts
// utils/http.ts
import axios from "axios";
import Qs from "qs";
import { isFormData, getRandomId } from "@/utils/helper";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

const http = axios.create({
    timeout: 30 * 1000,
    baseURL: "/api",
});

let httpNum = 0;

const addHttp = () => {
    if (httpNum === 0) {
        NProgress.start();
    }
    httpNum++;
};

const finishHttp = () => {
    httpNum--;
    if (httpNum <= 0) {
        NProgress.done();
    }
};

// http request 拦截器
http.interceptors.request.use(
    (config) => {
        addHttp();
        // 添加请求 id
        if (config.url) {
            const reqId = getReqId();
            config.url = config.url.includes("?") ? `${config.url}&reqId=${reqId}` : `${config.url}?reqId=${reqId}`;
        }
        // 添加 token
        const token = localStorage.getItem("token") || "";
        if (token) {
            config.headers!.Authorization = token;
        }
        // post 请求格式化入参
        if (config.method === "post" && !isFormData(config.data)) {
            // 上传文件不能使用 QS 去格式化
            config.data = Qs.stringify(config.data);
        }
        return config;
    },
    (err) => {
        return Promise.reject(err);
    }
);

// http response 拦截器
http.interceptors.response.use(
    (res) => {
        finishHttp();
        if (res.data.errno === 999) {
            console.log("token过期");
        }
        return res;
    },
    (error) => {
        return Promise.reject(error);
    }
);
export default http;

Utils

ts
// utils.helper.ts

// await-to-js
export const to = <T, U = Error>(func: Promise<T>, error?: U): Promise<[null, T] | [U, null]> => func.then<[null, T]>((res: T) => [null, res]).catch<[U, null]>((err: U) => [err, null]);

// 生成随机 ID
export const getRandomId = (): string => Date.now().toString() + (Math.random() * 10000 * 10000).toFixed();

// 判断数据类型是否是 formData
export const isFormData = (dataany) => Object.prototype.toString.call(data) === '[object FormData]'

本站中引用到的其他资料,如有侵权,请联系本人删除