实现的功能
环境
- node版本: v18.13.0
- pnpm版本: 9.0.6
前置知识(待补充链接)
- pnpm、npm、yarm
- vite、webpack
步骤
1. 初始化项目
pnpm create vite vite_react_project_template --template react
2. 安装依赖
pnpm install react-router-dom @ant-design/icons antd
3. 配置eslint和prettier
初始化项目的时候,已经内置了eslint,但是并没有prettier。
- 我们打开初始化项目的/src/App.jsx文件,随便修改点错误语法,会发现已经有提示了!但是格式的错误并没有错误提示!
- package.json里面也已经有了对应的依赖和执行命令
- .eslintrc.cjs文件里也有了相关配置
现在来解决格式错误的高亮提示问题!
- 安装prettier
pnpm install prettier -D
2.安装 ESLint 插件
为了让 ESLint 和 Prettier 协同工作,你需要安装一些 ESLint 插件。以下是一些常用的插件:
eslint-plugin-prettier: 用于将 Prettier 的规则集成到 ESLint 中。eslint-config-prettier: 用于关闭所有不必要的或可能与 Prettier 冲突的规则。
安装这些插件:
pnpm install eslint-plugin-prettier eslint-config-prettier -D
- 修改
.eslintrc.js文件 修改后如下:
- 修改
package.json文件 修改后如下:
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prettier": {
"semi": false,
"singleQuote": true,
"tabWidth": 2
}
- 查看效果 此时再看/src/App.jsx文件,会发现错误的格式已经标注出来了
ps:因为后面引入了typescript,所以现在配置的eslint不满足需求了,需要追加支持ts的插件
- 安装插件
pnpm install @typescript-eslint/eslint-plugin @typescript-eslint/parser -D - 修改
.eslintrc.js
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"plugin:@typescript-eslint/recommended"
// 可能还有其他的扩展配置
],
"rules": { // 自定义规则 } }
修改完后如下:
4.配置typescipt
- 安装
pnpm install typescript -D - 初始化
npx tsc --init执行后会生成tsconfig.json文件,将文件内容改为
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"sourceMap": true,
"skipLibCheck": true,
"isolatedModules": true,
"allowJs": true,
"lib": [
"ESNext",
"DOM"
],
"baseUrl": ".",
"jsx": "react",
"outDir": ".",
"paths": {
"@src/*": [
"./src/*"
],
},
},
"exclude": ["*.js", "build", "env", "static"]
}
- 重命名文件扩展名。将React组件从
.js文件扩展名改为.tsx。例如,App.js应该改为App.tsx。
问题解决: 引入ts后,会发现项目里面多了很多报错
main.tsx文件里面的document.getElementById("root")飘红,并提示类型“HTMLElement | null”的参数不能赋给类型“Container”的参数。 不能将类型“null”分配给类型“Container”
原因: 这个错误是因为
ReactDOM.createRoot方法期望一个有效的 DOM 元素作为参数,但是document.getElementById("root")可能返回null,这意味着没有找到具有 ID 为 "root" 的元素。这通常发生在你的 HTML 文件中没有一个元素具有该 ID,或者你的脚本在 DOM 完全加载之前就运行了。 为了解决这个问题,你可以确保你的 HTML 文件中有一个具有 ID 为 "root" 的元素,或者你可以延迟执行你的脚本,直到 DOM 完全加载。
解决方法
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.js";
import "./index.css";
document.addEventListener('DOMContentLoaded', (event) => {
const rootElement = document.getElementById('root');
if (rootElement) {
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
});
5. 测试路由功能
- 调整项目结构
- 新增
src/components/Menu/index.tsx文件
import React from 'react';
import { Menu } from 'antd';
import { Link } from 'react-router-dom';
function AppMenu() {
return (
<Menu mode="horizontal">
<Menu.Item key="home">
<Link to="/">Home</Link>
</Menu.Item>
<Menu.Item key="about">
<Link to="/about">About</Link>
</Menu.Item>
</Menu>
);
}
export default AppMenu;
- 新增
src/pages/About.tsx文件
import React from 'react';
const About: React.FC = () => {
return <h1>About Page</h1>;
};
export default About;
- 新增
src/pages/Home.tsx文件
import React from 'react';
const Home: React.FC = () => {
return <h1>Home Page</h1>;
};
export default Home;
- 新增
src/routes/index.tsx文件
import React, { Suspense } from 'react'
import { RouteObject, useRoutes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
const routeObjects: RouteObject[] = [
{
path: '/',
element: (
<Suspense>
<Home />
</Suspense>
)
},
{
path: '/about',
element: (
<Suspense>
<About />
</Suspense>
),
}
]
function Pages() {
const routes = useRoutes(routeObjects)
return routes
}
export default Pages
- 修改
App.tsx文件
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import Menu from './components/Menu'
import Pages from './routes';
const App: React.FC = () => {
return (
<Router>
<Menu />
<Pages />
</Router>
);
};
export default App;
- 执行
pnpm dev启动项目,会发现路由配置成功了
6. 配置单元测试
- 安装vitest和相关依赖
pnpm add -D vitest jsdom @testing-library/react @testing-library/jest-dom @vitejs/plugin-react @vitest/coverage-v8
- 在
vite.config.ts文件中添加vitest插件
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/setupTests.ts'],
},
})
- 新增 src/setupTests.ts文件
import '@testing-library/jest-dom'
- 在
package.json文件中添加测试脚本:
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
}
- 新增一个单元测试文件src/components/Button/test/Button.spec.tsx
import { describe, it, expect } from 'vitest';
import { render, fireEvent } from '@testing-library/react';
import { Button } from 'antd';
import { vi } from 'vitest';
import React from 'react';
// 测试用例:测试按钮点击事件
describe('Button', () => {
it('should call onClick handler when clicked', () => {
const handleClick = vi.fn(); // 创建一个模拟函数
const { getByText } = render(<Button onClick={handleClick}>Click Me</Button>);
const button = getByText('Click Me');
fireEvent.click(button); // 模拟点击事件
expect(handleClick).toHaveBeenCalledTimes(1); // 验证点击事件是否被调用
});
});
问题记录
-
- Button.spec.tsx一定是tsx类型文件,如果是ts类型会报错
排查很久。。。改为.tsx后问题解决
-
- 需要显式引用React
添加import React from 'react';后报错消失
- 执行
pnpm test发现单元测试已生效
- 执行
pnpm coverage查看覆盖率报告
7.配置husky
- 安装husky:
pnpm i husky -D
pnpm i lint-staged -D //让你只对本次提交的文件进行校验,而不是整个项目的所有文件
- 安装相关插件:
pnpm i @commitlint/config-conventional -D
pnpm i @commitlint/cli -D
- 配置一个 commitlint 内容插件来确定一种 msg 风格
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.ts
- 配置 .husky钩子
// commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit "$1"
// pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm run lint
# //需要 pnpm run prepare 生成 .husky 配置文件。不然commit 前的自动校验不会生效。
// pre-push
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm run test
这里用了默认的持续监听,所以需要手动按q来停止监听,但是提交前校验的时候我们不需要这个持续监听,所以修改下 pre-push文件
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm run test --watch=false
结语
至此,一个简单的拥有基本功能的项目算是搭建完成了,后续有新的功能会继续添加的!