vite-react-ts从0到1搭建
前言:React项目一般都是基于create-react-app等脚手架快速搭建,初始化项目。现在有点空闲的时间,琢磨如何从0到1进行搭建,加深自己对搭建项目的流程和熟悉。
搭建基础项目
一、初始化&基本配置
npm init && git init
修改package.json
{
"name": "react-ts-web",
"version": "1.0.0",
"description": "a react typescript web template",
"type": "module",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [
"react",
"typescript",
"vite"
],
"homepage": "https://github.com/xxx/xxx",
"bugs": {
"url": "https://github.com/xxx/xxx/issues"
},
"author": "xxx <xxx@xx.com>",
"license": "ISC"
}
二、安装核心依赖,搭建开发环境
1. 安装依赖
pnpm add react react-dom && pnpm add -D vite typescript @types/react-dom @types/react @types/node
2. tsconfig.json
初始化,修改
npx tsc --init
{
"references": [{ "path": "./tsconfig.vite.json" }], // 指定工程引用依赖
"compilerOptions": {
"target": "ES2020", // 指定编译的ECMAScript目标版本
"lib": ["ES2020", "DOM", "DOM.Iterable"], // 编译过程中需要引入的库文件的列表
"jsx": "react-jsx", // 指定 jsx 代码的生成,无需在每个jsx文件中引入React
"module": "ESNext", // 指定生成哪个模块系统代码
"moduleResolution": "Node", // 决定如何处理模块
"allowImportingTsExtensions": true, // 允许TypeScript文件通过TypeScript特定的扩展名如.ts, .mts, 或 .tsx互相导入
"resolveJsonModule": true, // 允许引入 JSON 文件
"allowJs": true, // 是否允许编译javascript文件
"noEmit": true, // 设置是否输出 js 文件,一般是设置为 false,将打包等工作交给 vite/webpack 等工具
"isolatedModules": true, // 将每个文件做为单独的模块
"esModuleInterop": true, // 支持合成模块的默认导入
"forceConsistentCasingInFileNames": true, // forceConsistentCasingInFileNames
"strict": true, // 启用所用严格的类型检查
"skipLibCheck": true, // 跳过对 .d.ts 文件的类型检查
"types": ["node", "vite/client"],
"baseUrl": ".", // 用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响
"paths": {
// 用于设置模块名到基于baseUrl的路径映射
"@/*": ["./src/*"]
}
},
"include": ["src", "@types"],
"exclude": ["node_modules"]
}
3. 新增tsconfig.vite.json
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
作用:
tsconfig.vite.json
专门为vite.config.ts
提供的ts配置文件。 单独配置原因: 运行环境不同,项目的代码运行在浏览器环境,而vite.config.ts
运行在Node环境。相应的两者需要的接口类型也各不一样。
4. 按照所示,搭建基础架子
App.tsx
export default function App() {
return (
<div>
<h1>Welcome to React Web.</h1>
</div>
);
}
main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const root = createRoot(document.querySelector('#app')!);
root.render(
<StrictMode>
<App />
</StrictMode>,
);
使用
StrictMode
来启用组件树内部的额外开发行为和警告
vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
});
修改 package.json
, 添加 vite
启动、打包脚本和 ts 类型检测,内容太多就只展示新增内容
{
...
"scripts": {
"serve": "vite",
"build": "vite build",
"tsc": "tsc --noEmit"
},
}
新增 .gitignore
。git提交时,忽略文件提交
node_modules
dist
.DS_Store
三、配置ESLint + prettier
eslint: 代码检查工具,主要用来发现代码错误、统一代码风格。 prettier: 对代码进行格式化,并不关注代码质量潜在问题的检查,倾向于团队的代码风格的规范或统一。
1. ESLint 配置
这里的 eslint 配置个人比较喜欢用json,就采用了json格式
2. 将 prettier 作为 ESLint 的规则来使用,让 ESLint 托管 prettier。
pnpm add -D eslint-config-prettier prettier eslint-plugin-prettier eslint-config-prettier
修改 .eslintrc.json
{
"root": true,
"parserOptions": {
"project": ["./tsconfig.json", "./tsconfig.vite.json"]
},
"settings": {
"react": {
"version": "detect"
}
},
"extends": ["alloy", "alloy/react", "alloy/typescript", "prettier"],
"plugins": ["prettier"],
"env": {
"browser": true
},
"globals": {},
"rules": {
"prettier/prettier": "error"
},
"ignorePatterns": ["dist", "node_modules"]
}
新增 .prettierrc
"eslint-config-alloy/.prettierrc.js"
3. vite.config.ts
配置ESLint
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
+ import eslint from 'vite-plugin-eslint'
export default defineConfig({
- plugins: [react()]
+ plugins: [react(), eslint()],
});
四、配置husky + lint-staged
husky: 给git添加hook钩子,在特定的 Git 操作触发之前或之后执行预定义的脚本 lint-staged: 在 Git 暂存区中运行指定脚本的工具。它通常与 Husky 一起使用
1. 安装husky、lint-staged依赖
pnpm dlx husky-init && pnpm install
pnpm add -D lint-staged
2. 新增 .lintstagedrc.json
{
"*.{js,jsx}": ["eslint --fix"],
"*.{ts,tsx}": ["eslint --fix", "bash -c tsc"]
}
通过
bash -c tsc
,让tsc识别项目中的tsconfig.json
配置
五、git commit 规范提交
1. 全局安装 commitizen
pnpm add commitizen -g
2. 项目根目录安装
pnpm add -D cz-git
3. package.json
新增内容
{
...
"scripts": {
"commit": "git-cz"
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
},
}
4. 添加commitlint.config.cjs
/** @type {import('cz-git').UserConfig} */
module.exports = {
rules: {
// @see: https://commitlint.js.org/#/reference-rules
},
prompt: {
alias: { fd: 'docs: fix typos' },
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixesSelect: '选择关联issue前缀(可选):',
customFooterPrefix: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?'
},
types: [
{ value: 'feat', name: 'feat: 新增功能 | A new feature' },
{ value: 'fix', name: 'fix: 修复缺陷 | A bug fix' },
{ value: 'docs', name: 'docs: 文档更新 | Documentation only changes' },
{ value: 'style', name: 'style: 代码格式 | Changes that do not affect the meaning of the code' },
{ value: 'refactor', name: 'refactor: 代码重构 | A code change that neither fixes a bug nor adds a feature' },
{ value: 'perf', name: 'perf: 性能提升 | A code change that improves performance' },
{ value: 'test', name: 'test: 测试相关 | Adding missing tests or correcting existing tests' },
{ value: 'build', name: 'build: 构建相关 | Changes that affect the build system or external dependencies' },
{ value: 'ci', name: 'ci: 持续集成 | Changes to our CI configuration files and scripts' },
{ value: 'revert', name: 'revert: 回退代码 | Revert to a commit' },
{ value: 'chore', name: 'chore: 其他修改 | Other changes that do not modify src or test files' },
],
useEmoji: false,
emojiAlign: 'center',
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixes: [
// 如果使用 gitee 作为开发管理
{ value: 'link', name: 'link: 链接 ISSUES 进行中' },
{ value: 'closed', name: 'closed: 标记 ISSUES 已完成' }
],
customIssuePrefixAlign: 'top',
emptyIssuePrefixAlias: 'skip',
customIssuePrefixAlias: 'custom',
allowCustomIssuePrefix: true,
allowEmptyIssuePrefix: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: ''
}
}
六、添加changelog
pnpm add standard-version
1. package.json
添加脚本
{
...
"release": "standard-version"
}
2. 首次生成changelog
version:1.0.0版本
npx standard-version --first-release
功能
一、SASS支持
pnpm add -D sass
1. 新增文件 src/App.scss
.main {
h1 {
color: red;
}
}
2. src/App.tsx
修改
import './App.scss';
export default function App() {
return (
<div className="main">
<h1>Welcome to React Web.</h1>
</div>
);
}
二、图片支持
1. 新增 ./src/assets/react.png
2. src/App.tsx
修改
import './App.scss';
import ReactLogo from './assets/React.png';
export default function App() {
return (
<div className="main">
<h1>Welcome to React Web.</h1>
<img src={ReactLogo} alt="logo" />
</div>
);
}
3. 新增@types/assets.d.ts
declare module '*.png';
declare module '*.svg';
declare module '*.jpeg';
declare module '*.jpg';
三、设置路径别名
1. vite.config.ts
修改
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import eslint from 'vite-plugin-eslint';
+ import path from 'path';
export default defineConfig({
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, 'src'),
+ },
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ },
plugins: [react(), eslint()],
});
2. eslint 修改
pnpm add eslint-import-resolver-alias
.eslintrc.json
{
"root": true,
"parserOptions": {
"project": ["./tsconfig.json", "./tsconfig.vite.json"]
},
"settings": {
"react": {
"version": "detect"
},
+ "import/resolver": {
+ "alias": [["@", "./src"]]
+ }
},
"extends": ["alloy", "alloy/react", "alloy/typescript", "prettier"],
"plugins": ["prettier"],
"env": {
"browser": true
},
"globals": {},
"rules": {
"prettier/prettier": "error",
"no-return-assign": "off"
},
"ignorePatterns": ["dist", "node_modules"]
}
3. tsconfig.json
修改
{
"references": [{ "path": "./tsconfig.vite.json" }], // 指定工程引用依赖
"compilerOptions": {
"target": "ES2020", // 指定编译的ECMAScript目标版本
"lib": ["ES2020", "DOM", "DOM.Iterable"], // 编译过程中需要引入的库文件的列表
"jsx": "react-jsx", // 指定 jsx 代码的生成,无需在每个jsx文件中引入React
"module": "ESNext", // 指定生成哪个模块系统代码
"moduleResolution": "Node", // 决定如何处理模块
"allowImportingTsExtensions": true, // 允许TypeScript文件通过TypeScript特定的扩展名如.ts, .mts, 或 .tsx互相导入
"resolveJsonModule": true, // 允许引入 JSON 文件
"allowJs": true, // 是否允许编译javascript文件
"noEmit": true, // 设置是否输出 js 文件,一般是设置为 false,将打包等工作交给 vite/webpack 等工具
"isolatedModules": true, // 将每个文件做为单独的模块
"esModuleInterop": true, // 支持合成模块的默认导入
"forceConsistentCasingInFileNames": true, // forceConsistentCasingInFileNames
"strict": true, // 启用所用严格的类型检查
"skipLibCheck": true, // 跳过对 .d.ts 文件的类型检查
+ "baseUrl": ".", // 用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响
+ "paths": { // 用于设置模块名到基于baseUrl的路径映射
+ "@/*": ["./src/*"]
+ }
},
"include": ["src"],
"exclude": ["node_modules"]
}
四、React路由配置
1. 安装依赖
pnpm add react-router-dom
2. 新增页面
src/pages/404.tsx
import { useNavigate } from 'react-router-dom';
export default () => {
const navigate = useNavigate();
const goBack = () => {
navigate(-1);
};
return (
<>
<h1>About Page</h1>
<button onClick={goBack}>返回</button>
</>
);
};
src/pages/Home.tsx
import { Link } from 'react-router-dom';
export default () => {
return (
<>
<h1>Home Page</h1>
<Link to="/about">页面跳转</Link>
</>
);
};
src/pages/About.tsx
import { useNavigate } from 'react-router-dom';
export default () => {
const navigate = useNavigate();
const goBack = () => {
navigate(-1);
};
return (
<>
<h1>About Page</h1>
<button onClick={goBack}>返回</button>
</>
);
};
3. 移除src/App.tsx
4. 定义路由表src/router/index.ts
import About from '../pages/About';
import Home from '../pages/Home';
import NoFound from '../pages/404';
import { RouteObject } from 'react-router-dom';
const routeConfig: RouteObject[] = [
{
path: '/',
Component: Home,
},
{
path: '/about',
Component: About,
},
{
path: '*',
Component: NoFound,
},
];
export default routeConfig;
5. 入口文件main.tsx
调整
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import routeConfig from './router';
const router = createBrowserRouter(routeConfig);
const root = createRoot(document.querySelector('#app')!);
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
五、状态管理机制
目前 React 的状态管理机制解决方案是百花齐放,如:
redux
、react-toolkit
、zustand
、recoil
等。为了降低上手成本,这里使用目前比较火的zustand
。具体就不展开对比各种状态管理机制的不同了。
1. 安装依赖
pnpm add zustand
2. 新增 src/store/counter.ts
import { create } from 'zustand';
interface CounterStore {
num: number;
increase: () => void;
}
const useCounterStore = create<CounterStore>()((set) => ({
num: 0,
increase: () => set((state) => ({ num: (state.num += 1) })),
}));
export default useCounterStore;
3. 新增组件 src/components/CountNum.tsx
import useCounterStore from '@/store/counter';
export default () => {
const num = useCounterStore((state) => state.num);
return <p>{num} </p>;
};
4. 修改 src/pages/Home.tsx
import { Link } from 'react-router-dom';
+ import useCounterStore from '@/store/counter';
+ import CountNum from '@/components/CountNum';
export default () => {
+ const increase = useCounterStore((state) => state.increase);
return (
<>
<h1>Home Page</h1>
+ <CountNum />
+ <button onClick={increase}>加一</button>
+ <br />
<Link to="/about">页面跳转</Link>
</>
);
};
六、 多环境打包
可以为每个不同的环境,设置特定的内容:接口请求地址、OSS配置等
1. 新增配置文件
env/.env.development
VITE_PROJECT_ENV=development
VITE_APP_TITLE=My App (development)
env/.env.production
VITE_PROJECT_ENV=production
VITE_APP_TITLE=My App (production)
env/.env.staging
VITE_PROJECT_ENV=staging
VITE_APP_TITLE=My App (staging)
env/.env.testing
VITE_PROJECT_ENV=testing
VITE_APP_TITLE=My App (testing)
2. vite.config.ts
调整
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import eslint from 'vite-plugin-eslint';
import path from 'path';
export default defineConfig({
+ envDir: './env',
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
plugins: [react(), eslint()],
});
3. package.json
添加脚本命令
...
"scripts": {
...
"start:dev": "vite --mode development",
"start:test": "vite --mode testing",
"start:staging": "vite --mode staging",
"start:prod": "vite --mode production",
"build": "pnpm build:prod",
"build:dev": "pnpm tsc && vite build --mode development",
"build:test": "pnpm tsc && vite build --mode testing",
"build:staging": "pnpm tsc && vite build --mode staging",
"build:prod": "pnpm tsc && vite build --mode production",
}
4. 页面模版修改
import { Link } from 'react-router-dom';
import useCounterStore from '@/store/counter';
import CountNum from '@/components/CountNum';
+ import { useState } from 'react';
export default () => {
const increase = useCounterStore((state) => state.increase);
+ const title = useState(import.meta.env.VITE_APP_TITLE);
return (
<>
- <h1>Home Page</h1>
+ <p>{title}</p>
<CountNum />
<button onClick={increase}>加一</button>
<br />
<Link to="/about">页面跳转</Link>
</>
);
};
七、CSS模块化
TIPS:后续内容,因为调整内容过多,只展示关键内容,具体修改请看下方github链接
React CSS模块化的方案有
css in js
和css module
。两者各有优点,个人经常使用vue
,比较习惯css module
的方案。这里就只用css module
1. vite.config.ts
修改
export default defineConfig({
...
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/assets/scss/mixin.scss";',
},
},
modules: {
localsConvention: 'camelCaseOnly',
},
}
});
additionalData:指项目的引入的scss变量(主题色等)
2.目录调整整合
每个组件/页面一个文件夹维护,存放index.tsx
和indes.module.scss
;
对应的**.scss
-> **.module.scss
3. 安装插件
使用vscode
的同学,可以安装CSS Modules
的插件,提供语法高亮和智能提示,使用起来比较方便
八、React 路由懒加载,嵌套路由
1. 嵌套路由
把页面拆分成
header
、footer
和content
。要控制的只是content
的路由区域
创建一个src/layouts/basicLayout
import React from 'react';
import { Outlet } from 'react-router-dom';
import Header from '../../components/Header';
import Footer from '../../components/Footer';
import styles from './index.module.scss';
const BasicLayout: React.FC = () => {
return (
<>
<Header>This is Header.</Header>
<div className={styles.content}>
<Outlet />
</div>
<Footer>This is Footer.</Footer>
</>
);
};
export default BasicLayout;
2. 路由表修改
import { RouteObject } from 'react-router-dom';
import { lazy } from 'react';
const routeConfig: RouteObject[] = [
{
path: '/',
Component: lazy(() => import('../layouts/basicLayout')),
children: [
{
path: '/',
Component: lazy(() => import('../pages/home')),
},
{
path: '/about',
Component: lazy(() => import('../pages/about')),
},
],
},
{
path: '*',
Component: lazy(() => import('../pages/noFound')),
},
];
export default routeConfig;
3. src/main.tsx
修改
import { StrictMode, Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import routeConfig from './router';
import './assets/scss/app.scss';
import './assets/scss/reset.scss';
import Loading from '@/components/Loading';
const router = createBrowserRouter(routeConfig);
const root = createRoot(document.querySelector('#app')!);
root.render(
<StrictMode>
<Suspense fallback={<Loading />}>
<RouterProvider router={router} />
</Suspense>
</StrictMode>,
);
Suspense
:路由懒加载时,页面加载过程中,会展示fallback
的Loading
组件
九、Axios请求库
1. 安装依赖
pnpm add axios
2.env
目录添加对应环境的接口请求地址
VITE_PROJECT_ENV=development
VITE_APP_TITLE=My App (development)
VITE_BASE_API_URL=https://mock.apifox.cn/m1/3175341-0-default
这里使用
apifox
mock 数据来展示例子
3. src/instance.ts
封装axios
实例
import axios, { AxiosResponse, AxiosError } from 'axios';
const instance = axios.create({
baseURL: import.meta.env.VITE_BASE_API_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
});
const errorCodeMap = new Map([
[401, '未授权,请登录'],
[404, '请求地址不存在'],
[500, '服务内部错误'],
[403, '用户不存在访问权限'],
]);
instance.interceptors.response.use(
(response: AxiosResponse) => {
return response.data;
},
(error: AxiosError) => {
const { response } = error;
if (!response) {
return Promise.reject(error);
}
if (errorCodeMap.has(response.status)) {
const errorMsg = errorCodeMap.get(response.status);
// TODO: 处理HTTP码异常逻辑
console.log(errorMsg);
}
return Promise.reject(response.data);
},
);
export default instance;
这里简单的封装了下,如何处理HTTP异常错误,还是需要根据开发同学的具体业务来实现。
4. src/api.index.ts
管理接口
import instance from './instance';
export const init = () => instance.get('/init');
理想状态下应该利用
typescript
定义入参类型和数据响应类型。那样子在调用接口的时候,就有对应的类型提示。这里也不做展开了
总结
整个搭建的过程也算是结束了,可以满足大部分的页面需求。有一些搭建内容缺少最佳实践,这部分就不做展开了,毕竟重点不在这,需要同学们慢慢琢磨。 后续项目代码可能会持续更新,但这里的文章到这里就结束了! 项目代码可以参考下面👇
项目代码
参考文献
转载自:https://juejin.cn/post/7260386796206506045