1. 通过 Create-React-App 创建项目
- 创建一个
TypeScript
模版的React
项目
npx create-react-app react-app --template typescript
- 运行项目
cd react-app
npm start
- 输入
localhost:3000
显示如下如即成功
2. 配置 CRACO
CRACO
全称 Create React App Configuration Override
,取首字母即组成了工具名称。是为了无 eject
、可配置式的去修改 CRA
默认提供的工程配置,这样既能享受 CRA
带来的便利和后续升级,也能自己去自定义打包配置完成项目需要,一举两得。
- 从npm安装最新版本的包作为开发依赖项:
npm i -D @craco/craco
- 在项目的根目录中创建一个CRACO配置文件并配置:
react-app
├── node_modules
+ ├── craco.config.js
└── package.json
- 更新
package.json
的脚本部分中对react
脚本的现有调用以使用CRACO CLI
:
"scripts": {
- "start": "react-scripts start"
+ "start": "craco start"
- "build": "react-scripts build"
+ "build": "craco build"
- "test": "react-scripts test"
+ "test": "craco test"
}
- 支持
TypeScript
,使用CRACO
提供的类型包
npm i -D @craco/types
- craco.config.js 配置
因为不同的项目有不同的需求和业务,配置文件也会不同,根据自己需求配置即可,遇到问题可到找craco官方文档 查看
下面我以配置 less
和别名为例:
安装craco-less
npm install -D craco-less
如果上面 craco-less
因为版本原因报错,在命令后面加 @alpha
。
配置craco-less插件和别名
const path = require("path");
const CracoLessPlugin = require("craco-less");
const resolve = (pathname) => path.resolve(__dirname, pathname);
module.exports = {
plugins: [
/* less */
{
plugin: CracoLessPlugin,
},
],
webpack: {
/* 别名 */
alias: {
"@": resolve("src"),
},
},
};
- 在
tsconfig.json
的compilerOptions
添加配置
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
- 运行
npm run start
,项目能正常跑起来就OK。
3. 集成 EditorConfig 配置
EditorConfig
有助于为不同 IDE
编辑器上处理同一项目的多个开发人员维护一致的编码风格。
# http://editorconfig.org
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行尾的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
VSCode
需要安装一个插件:EditorConfig for VS Code
4. 使用 Prettier 工具
Prettier
是一款强大的代码格式化工具,支持 JavaScript
、TypeScript
、CSS
、SCSS
、Less
、JSX
、Angular
、Vue
、GraphQL
、JSON
、Markdown
等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。
1.安装 Prettier
npm install prettier -D
2.配置 .prettierrc
文件:
- useTabs:使用tab缩进还是空格缩进,选择false;
- tabWidth:tab是空格的情况下,是几个空格,选择2个;
- printWidth:当行字符的长度,推荐80,也有人喜欢100或者120;
- singleQuote:使用单引号还是双引号,选择true,使用单引号;
- trailingComma:在多行输入的尾逗号是否添加,设置为
none
,比如对象类型的最后一个属性后面是否加一个,; - semi:语句末尾是否要加分号,默认值true,选择false表示不加;
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"trailingComma": "none",
"semi": false
}
3.创建 .prettierignore
忽略文件
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
VSCode
需要安装Prettier
的插件
VSCode
中的配置
- settings =>format on save => 勾选上
- settings => editor default format => 选择 prettier
6.测试 Prettier
是否生效
- 测试一:在代码中保存代码;
- 测试二:配置一次性修改的命令;
在package.json中配置一个scripts:
"prettier": "prettier --write ."
5. 使用 ESLint 检测
- 安装
ESLint
npm install eslint -D
- 配置
ESLint
npx eslint --init
第一步选择如何使用 ESLint
,选第二个
第二部模块化选择 ESModule
第三步选择框架,根据实际情况选择 React
第四步选择是否 TypeScript
,根据实际情况选择
第五步选择代码运行的环境,两个可以同时选择
第六步,配置文件类型, 选 js
第七步,根据你上面的选择询问你要不要安装包,我上面选了React和TypeScript
VSCode
需要安装ESLint
插件:
- 解决
ESLint
和Prettier
冲突的问题:
安装插件:
npm install eslint-plugin-prettier eslint-config-prettier -D
添加 Prettier
插件:plugin:prettier/recommended
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended'
],
overrides: [],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['react', '@typescript-eslint'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
'prettier/prettier': 'warn',
'@typescript-eslint/no-explicit-any': 'off'
},
settings: {
react: {
version: 'detect'
}
}
}
- VSCode中eslint的配置
"eslint.alwaysShowStatus": true,
- 在package.json中配置一个scripts:
"lint": "eslint ."
6. Git Husky和 ESLint (可选)
虽然现在已经要求项目使用 ESLint
了,但是不能保证组员提交代码之前都将 ESLint
中的问题解决掉了:
- 也就是希望保证代码仓库中的代码都是符合
ESLint
规范的; - 那么需要在组员执行
git commit
命令的时候对其进行校验,如果不符合eslint规范,那么自动通过规范进行修复;
那么如何做到这一点呢?可以通过 Husky
工具:
husky
是一个git hook
工具,可以帮助我们触发git
提交的各个阶段:pre-commit
、commit-msg
、pre-push
如何使用 husky
呢?
这里我们可以使用自动配置命令:
npx husky-init && npm install
这里会做三件事:
1.安装husky相关的依赖:
2.在项目目录下创建 .husky
文件夹:
3.在package.json中添加一个脚本:
4.接下来,我们需要去完成一个操作:在进行commit时,执行lint脚本:
这个时候我们执行 git commit
的时候会自动对代码进行 lint
校验。
7. Git Commit 规范 (可选)
7.1 代码提交风格
通常我们的 git commit
会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制。
但是如果每次手动来编写这些是比较麻烦的事情,我们可以使用一个工具:Commitizen
Commitizen
是一个帮助我们编写规范commit message
的工具;
1.安装 Commitizen
npm install commitizen -D
2.安装 cz-conventional-changelog
,并且初始化 cz-conventional-changelog
:
npx commitizen init cz-conventional-changelog --save-dev --save-exact
这个命令会帮助我们安装cz-conventional-changelog:
并且在package.json中进行配置:
这个时候我们提交代码需要使用 npx cz
:
- 第一步是选择type,本次更新的类型
Type | 作用 |
---|---|
feat | 新增特性 (feature) |
fix | 修复 Bug(bug fix) |
docs | 修改文档 (documentation) |
style | 代码格式修改(white-space, formatting, missing semi colons, etc) |
refactor | 代码重构(refactor) |
perf | 改善性能(A code change that improves performance) |
test | 测试(when adding missing tests) |
build | 变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等) |
ci | 更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等 |
chore | 变更构建流程或辅助工具(比如更改测试环境) |
revert | 代码回退 |
- 第二步选择本次修改的范围(作用域)
- 第三步选择提交的信息
- 第四步提交详细的描述信息
- 第五步是否是一次重大的更改
- 第六步是否影响某个open issue
我们也可以在scripts中构建一个命令来执行 cz:
7.2 代码提交验证
如果我们按照 cz
来规范了提交风格,但是依然有同事通过 git commit
按照不规范的格式提交应该怎么办呢?
- 我们可以通过
commitlint
来限制提交;
1.安装 @commitlint/config-conventional 和 @commitlint/cli
npm i @commitlint/config-conventional @commitlint/cli -D
2.在根目录创建commitlint.config.js文件,配置commitlint
module.exports = {
extends: ['@commitlint/config-conventional']
}
3.使用husky生成commit-msg文件,验证提交信息:
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
8. 文件目录结构划分
对项目进行目录结构划分:
react-app
+ |- /src
+ |- /assets 存放资源
+ |- /img
+ |- /css
+ |- /font
+ |- /data
+ |- base-ui 存放多个项目中都会用到的公共组件
+ |- components 存放这个项目用到的公共组件
+ |- hooks 存放自定义hook
+ |- views 视图
+ |- store 状态管理
+ |- router 路由
+ |- service 网络请求
+ |- utils 工具
+ |- global 全局注册、全局常量...
- |- App.css
- |- App.test.tsx
- |- index.css
- |- logo.svg
- |- reportWebVitals.ts
- |- setupTest.ts
App.tsx
import React from 'react'
function App() {
return (
<div className="App">
<h1>React App</h1>
</div>
)
}
export default App
index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(<App />)
9. CSS 重置
- 添加
common.less
index.less
reset.less
src/assets/css
+ |- common.less 公共样式
+ |- index.less
+ |- reset.less 自定义重置样式
reset.less
/* reset.css样式重置文件 */
/* margin/padding重置 */
body, h1, h2, h3, ul, ol, li, p, dl, dt, dd {
padding: 0;
margin: 0;
}
/* a元素重置 */
a {
text-decoration: none;
color: #333;
}
/* img的vertical-align重置 */
img {
vertical-align: top;
}
/* ul, ol, li重置 */
ul, ol, li {
list-style: none;
}
/* 对斜体元素重置 */
i, em {
font-style: normal;
}
index.less
@import './reset.less';
@import './common.less';
- 安装
normalize.css
包
npm install normalize.css
- 在
index.tsx
中引入normalize.css
和index.less
...
import 'normalize.css'
import './src/assets/index.less'
...
10. 设置代码片段
为了方便开发,我们可以设置一份通用的 React
组件代码模板。
- 创建一份自己常用的模板
import React, { memo } from 'react'
import type { FC, ReactNode } from 'react'
interface IProps {
children?: ReactNode
}
const Template: FC<IProps> = () => {
return <div>Template</div>
}
export default memo(Template)
- 复制到 snippet-generator网站 ,生成对应vscode的配置
vscode
中点击 文件->首选项->配置用户代码片段, 选择typescriptreact.json
配置文件
- 将生成的代码 变量名修改为
${1:Template}
复制进去 - 之后只要在文件中输入
tsreact
即可创建模板
11. 配置 React-Router
- 安装
react-router-dom
包
npm i react-router-dom
- 在
src/index.tsx
中导入HashRouter
对App组件进行包裹
import { HashRouter } from 'react-router-dom'
root.render(
<HashRouter>
<App />
</HashRouter>
)
- 在
src/router/index.tsx
中配置路由映射表
import React, { lazy } from 'react'
import type { RouteObject } from 'react-router-dom'
import { Navigate } from 'react-router-dom'
/* 路由懒加载 */
const Home = lazy(() => import('@/views/home'))
const Mine = lazy(() => import('@/views/mine'))
const routes: RouteObject[] = [
{
path: '/',
element: <Navigate to="/home" />
},
{
path: '/home',
element: <Home />
},
{
path: '/mine',
element: <Mine />
}
]
export default routes
- 在
App.tsx
中使用路由
import React, { Suspense } from 'react'
import { useRoutes, Link } from 'react-router-dom'
import routes from './router'
function App() {
return (
<div className="App">
<div className="nav">
<Link to="/home">菜单一</Link>
<Link to="/mine">菜单二</Link>
</div>
<Suspense fallback="loading...">
<div className="main">{useRoutes(routes)}</div>
</Suspense>
</div>
)
}
export default App
12. 配置 Redux 状态管理
- 安装
react-redux
和@reduxjs/toolkit
两个包
npm install react-redux @reduxjs/toolkit
- 在
src/store
中创建store
store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import couterReducer from './modules/counter'
import {
useSelector,
TypedUseSelectorHook,
useDispatch,
shallowEqual
} from 'react-redux'
const store = configureStore({
reducer: {
counter: couterReducer
}
})
type GetStateFnType = typeof store.getState
type IRootState = ReturnType<GetStateFnType>
type DispatchType = typeof store.dispatch
export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
export const useAppDispatch: () => DispatchType = useDispatch
export const shallowEqualApp = shallowEqual
export default store
store/modules/counter.ts
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
count: 0,
message: 'Hello Redux'
},
reducers: {}
})
export default counterSlice.reducer
- 在
src/index.tsx
中提供store
import { Provider } from 'react-redux'
import store from './store'
root.render(
<Provider store={store}>
<HashRouter>
<App />
</HashRouter>
</Provider>
)
- 在 App.tsx 测试使用
获取并且显示 state
import { useAppSelector, shallowEqualApp } from './store'
...
const { count, message } = useAppSelector(
(state) => ({
count: state.counter.count,
message: state.counter.message
}),
shallowEqualApp
)
...
return (
...
<div>count:{count}</div>
<div>message:{message}</div>
...
)
修改 state
import { useAppDispatch } from './store'
...
const dispatch = useAppDispatch()
function handleChangeMessage() {
dispatch(changeMessage('哈哈哈哈哈哈'))
}
...
return (
...
<button onClick={handleChangeMessage}>changeMessage</button>
...
)
13. 环境配置
在根目录下添加两个文件用以配置不同环境下的环境变量
.env.development
REACT_APP_BASE_URL = 'www.development.com'
.env.production
REACT_APP_BASE_URL = 'www.production.com'
同时需要在 react-app-env.d.ts 声明变量类型
/// <reference types="react-scripts" />
declare namespace NodeJS {
interface ProcessEnv {
readonly REACT_APP_BASE_URL: string
}
}
14. axios 网络请求封装
修改 service
的目录结构为
+ |- /config
+ |- index.ts
+ |- /request
+ |- index.ts
+ |- types.ts
+ |- index.ts
/service/config.ts
const BASE_URL = process.env.REACT_APP_BASE_URL
export const TIME_OUT = 1000
export { BASE_URL }
/service/request/index.ts
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { RequestConfig } from './type'
// 拦截器: 蒙版Loading/token/修改配置
/**
* 两个难点:
* 1.拦截器进行精细控制
* > 全局拦截器
* > 实例拦截器
* > 单次请求拦截器
*
* 2.响应结果的类型处理(泛型)
*/
class Request {
instance: AxiosInstance
// request实例 => axios的实例
constructor(config: RequestConfig) {
this.instance = axios.create(config)
// 每个instance实例都添加拦截器
this.instance.interceptors.request.use(
(config) => {
// loading/token
return config
},
(err) => {
return err
}
)
this.instance.interceptors.response.use(
(res) => {
return res.data
},
(err) => {
return err
}
)
// 针对特定的Request实例添加拦截器
this.instance.interceptors.request.use(
config.interceptors?.requestSuccessFn,
config.interceptors?.requestFailureFn
)
this.instance.interceptors.response.use(
config.interceptors?.responseSuccessFn,
config.interceptors?.responseFailureFn
)
}
// 封装网络请求的方法
request<T = any>(config: RequestConfig<T>) {
// 单次请求的成功拦截处理
if (config.interceptors?.requestSuccessFn) {
config = config.interceptors.requestSuccessFn(config)
}
// 返回Promise
return new Promise<T>((resolve, reject) => {
this.instance
.request<any, T>(config)
.then((res) => {
// 单词响应的成功拦截处理
if (config.interceptors?.responseSuccessFn) {
res = config.interceptors.responseSuccessFn(res)
}
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
get<T = any>(config: RequestConfig<T>) {
return this.request({ ...config, method: 'GET' })
}
post<T = any>(config: RequestConfig<T>) {
return this.request({ ...config, method: 'POST' })
}
delete<T = any>(config: RequestConfig<T>) {
return this.request({ ...config, method: 'DELETE' })
}
patch<T = any>(config: RequestConfig<T>) {
return this.request({ ...config, method: 'PATCH' })
}
}
export default Request
/service/request/type.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
// 针对AxiosRequestConfig配置进行扩展
export interface Interceptors<T = AxiosResponse> {
requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestFailureFn?: (err: any) => any
responseSuccessFn?: (res: T) => T
responseFailureFn?: (err: any) => any
}
export interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: Interceptors<T>
}
/service/index.ts
import { BASE_URL, TIME_OUT } from './config'
import Request from './request'
const request = new Request({
baseURL: BASE_URL,
timeout: TIME_OUT
})
export default request
15. 引入 styled-components
除了 styled-components本身之外还要安装它的类型声明
npm i -D styled-components @types/styled-components