using React Hook + Webpack5 + React Router6 + Typescript + Jest + React Testing Library
一个入门3个月起步的项目,改造2两年之际,提炼出此项目原型。 对前端技术有兴趣,不满足于日常业务开发的同学可以一起完善、互相学习。
- master:基础项目配置,以此分支为基础,可以对感兴趣的前端技术进行拓展、验证、实践。
- react-hook-webpack-ts-jest-start:以实际项目升级、改造程中沉淀的知识为基础,提炼出此项目原型。
工具选择
我选用的nvm, 按照github官方命令安装报错:Failed to connect to raw.githubusercontent.com port 443: Connection refused
解决方法:
- 查询真实IP:在 这里 查询raw.githubusercontent.com的真实IP。
- 修改host: sudo vim /etc/hosts
- 在上边文件中添加查到的IP: 199.232.68.133 raw.githubusercontent.com(多个IP可以都是试试)
以前可以借助avn在切换项目的时候自动化切换node版本,我在项目中试了下不行,可能是avn长时间不维护了命令执行的时候会报错。
所以只配置了.nvmrc文件,切换项目后需要手动执行 nvm use切换node。
- Typescript、Babel对比
| 编译能力 | 类型检查 | 插件 | |
|---|---|---|---|
| tsc | ts(x),js(x) --> es 3/5/6... | 有 | 无 |
| Babel | ts(x),js(x) --> es 3/5/6... | 无 | 丰富 |
- Babel 7 之前,不支持 TS
编译流程是这样的:TS > TS 编译器 > JS > Babel > JS (再次) - Babel 7
实现了“只有一个 Javascript 编译器” 的梦想!通过允许 Babel 作为唯一的编译器来工作,就再也没必要利用一些复杂的 Webpack 魔法来管理、配置或者合并两个编译器。
Babel7处理TS实际上是移除了TS, 它将 TypeScript 全部转换为常规 JavaScript,然后再一如既往的操作。 - Webpack配置好 Babel后只具备编译功能,再安装TS补全类型检查功能。
然后我们新开一个 terminal,跑 npm run check-type。
npm i typescript -D
tsc --init// tsconfig.json
{
...
"compilerOptions":{
"noEmit":true // 不输出文件,只做类型检查
}
}// package.json
{
...
"script":{
...
"check-type": "tsc --watch"
}
}- 性能
加入ts-loader的webpack构建速度,肯定比只使用babel-loader慢,因为编译流程变长。
可以通过 fork-ts-checker-webpack-plugin 来缓解这种减速,它在单独的进程上运行 TypeScript 类型检查器。 - 如何选择 TypeScript 编译工具
- 如果没有使用 Babel,首选 TypeScript 自带编译器(配合 ts-loader 使用)
- 如果项目中有 Babel,安装 @babel/preset-typescript,配合 tsc 做类型检查。
- 两种编译器不要混用。
使用 Prettier 解决代码格式问题,使用 linter 解决代码质量问题
2019 年 1 月,TypeScript 官方决定全面采用 ESLint,之后也发布 typescript-eslint 项目,以集中解决 TypeScript 和 ESLint 兼容性问题。
而之前的两个 lint 解决方案都将弃用:
typescript-eslint-parser已停止维护- 在完成
ESLint功能后,将弃用TSLint并帮助用户迁移到ESLint
以前的项目中使用 eslint-loader 的问题:
遇到多次想在本地快速修改一个功能做验证的情况,但稍不留意 书写不规范就会lint不通过热编译出问题,页面加载失败,并且不看终端有时候还不知道问题出在哪,IDE没有提示。
大多数时候我就是单纯的想赶紧看下效果做验证,或者灵感来了想快点做验证,这个时候我并不关心这时候修改的代码到底规范不规范,或者即便不规范只要没出错,我也不想被迫立即进行调整,然后等待再次编辑,直到lint成功,这种打断很多时候让人极其不爽。
现在lint的执行是通过pre-commit & IDE配置进行的(IDE:Webstorm 配置就不截图了,有意者可以私下交流):
// package.json
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{tsx,ts,jsx,js,json,css,less,scss,md}": [
"eslint --fix",
"git add"
]
},关闭所有不必要或可能与 Prettier 冲突的规则。将 Prettier 作为 ESLint 规则运行,并将差异报告为单个 ESLint 问题。
该规则是可自动修复的——如果您使用 --fix 标志运行 eslint,您的代码将根据prettier的样式进行格式化。
按钮 HTML 元素的 type 属性的默认值是“submit”,这通常不是所需的行为,并且可能导致意外的页面重新加载。 此规则为所有按钮元素强制执行显式类型属性,并检查其值是否符合规范(即,是“button”、“submit”和“reset”之一)。
解决方法:显示的指明button的type
解决方法:建议使用模板字面量而非字符串连接
解决方法:eslint配置文件.eslintrc.cjs的rules中增加:
'react/jsx-filename-extension': ['error', {'extensions': ['.tsx', '.ts', '.jsx', '.js']}],
解决方法:在eslint配置文件.eslintrc.cjs顶层加入如下配置:
settings: {
"import/extensions": [".js", ".jsx", ".ts", ".tsx"],
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx"]
},
"import/resolver": {
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}
}
},
env: {
...redux文档中是这样说的:
You can safely import the RootState type from the store file here. It's a circular import, but the TypeScript compiler can correctly handle that for types. This may be needed for use cases like writing selector functions.
问题1:启用【Named export for CSS Modules】后, ts报错:Cannot find module ‘./index.module.css‘ or its corresponding type declarations
在全局ts -- global-env.d.ts 中增加如下内容:
/// <reference types="react-scripts" />
declare module '*.css' {
const content: { [className: string]: string }
export default content
}- Jest: 是一个令人愉快的 JavaScript 测试框架,专注于简单性。
- testing-library/jest-dom: @testing-library/jest-dom 库提供了一组自定义的 jest 匹配器,您可以使用它们来扩展 jest。 这些将使您的测试更具声明性,更易于阅读和维护。
- testing-library/react: React测试库, 是一个用于测试 React 组件的非常轻量级的解决方案。 它在 react-dom 和 react-dom/test-utils 之上提供了轻量级的实用功能,以鼓励更好的测试实践。 其主要指导原则是: 您的测试与您的软件使用方式越相似,它们能给您的信心就越大。
- testing-library/user-event: user-event 试图模拟用户与浏览器交互时会在浏览器中发生的真实事件。 例如 userEvent.click(checkbox) 会改变复选框的状态。
确保添加 expect.assertions 来验证一定数量的断言被调用。 否则一个fulfilled态的Promise 不会让测试失败︰
/** exmple */
//用 Promise.catch 测试一个异步的错误
it('tests error with promises', () => {
expect.assertions(1);
return user.getUserName(2).catch(e =>
expect(e).toEqual({
error: 'User with 2 not found.',
}),
);
});
// Or using async/await.
it('tests error with async/await', async () => {
expect.assertions(1);
try {
await user.getUserName(1);
} catch (e) {
expect(e).toEqual({
error: 'User with 1 not found.',
});
}
});
// 用`.rejects`.来测试一个异步的错误
it('tests error with rejects', () => {
expect.assertions(1);
return expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});
// 或者与async/await 一起使用 `.rejects`.
it('tests error with async/await and rejects', async () => {
expect.assertions(1);
await expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});解决方法:
- 创建src/mocks/matchMedia.ts
// src/__mocks__/matchmedia.ts
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
})- App.spec.tsx中导入
import '../__mocks__/matchMedia'解决方法:
/** 1 */
npm install --save-dev @babel/plugin-transform-runtime/** 2 */
// babel.config.js
plugins: !api.env('production')
? ['@babel/plugin-transform-runtime', 'react-refresh/babel']
: ['@babel/plugin-transform-runtime'],原因:可能antd就没考虑支持css modules。
// 在入口文件中做如下引入
import 'antd/dist/antd.css'解决方法:修改webpack配置
{
test: /\.css$/i,
exclude: [/\.module\.css$/i], // node_modules文件(其中的antd好像就没考虑支持css modules)、结尾没有.module的样式文件,不开启css modules。这时全局生效
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader, // 直接在development环境写MiniCssExtractPlugin.loader不行。
// { // 报错,组件的bug
// loader: 'thread-loader',
// options: cssWorkerPool,
// },
'css-loader',
'postcss-loader',
],
},
{
test: /\.css$/i,
include: [/\.module\.css$/i], // 处理node_modules以外的样式文件
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
{
// CSS 模块的命名导出, 被修改为以camelCase的形式
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
mode: 'local',
exportLocalsConvention: 'camelCase',
},
},
},
'postcss-loader',
],
},webpack alias中写入react别名后run start时终端报上边的错误。
解决方法:在alias中的react配置前加上 'react/jsx-runtime' 和 'react/jsx-dev-runtime'的配置
alias: {
'react/jsx-runtime': path.resolve(__dirname, '../node_modules/react/jsx-runtime'),
'react/jsx-dev-runtime': path.resolve(__dirname, '../node_modules/react/jsx-dev-runtime'),
react: path.resolve(__dirname, '../node_modules/react/index.js'),
},