本章的源码可以通过脚手架 jw-cli
下载
shnpm i @yejiwei/cli -g
接着执行以下命令即可
cd react-app npm install npm start
TypeScript
模版的 React
项目shellnpx create-react-app react-app --template typescript
shellcd react-app npm start
localhost:3000
显示如下如即成功CRACO
全称 Create React App Configuration Override
,取首字母即组成了工具名称。是为了无 eject
、可配置式的去修改 CRA
默认提供的工程配置,这样既能享受 CRA
带来的便利和后续升级,也能自己去自定义打包配置完成项目需要,一举两得。
shellnpm i -D @craco/craco
diff react-app
├── node_modules
+ ├── craco.config.js
└── package.json
package.json
的脚本部分中对 react
脚本的现有调用以使用 CRACO CLI
:diff"scripts": {
- "start": "react-scripts start"
+ "start": "craco start"
- "build": "react-scripts build"
+ "build": "craco build"
- "test": "react-scripts test"
+ "test": "craco test"
}
TypeScript
,使用 CRACO
提供的类型包shellnpm i -D @craco/types
因为不同的项目有不同的需求和业务,配置文件也会不同,根据自己需求配置即可,遇到问题可到找craco官方文档 查看
下面我以配置 less
和别名为例:
安装craco-less
shellnpm install -D craco-less
如果上面 craco-less
因为版本原因报错,在命令后面加 @alpha
。
配置craco-less插件和别名
jsconst 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
添加配置json "baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
npm run start
,项目能正常跑起来就OK。EditorConfig
有助于为不同 IDE
编辑器上处理同一项目的多个开发人员维护一致的编码风格。
yaml# 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
Prettier
是一款强大的代码格式化工具,支持 JavaScript
、TypeScript
、CSS
、SCSS
、Less
、JSX
、Angular
、Vue
、GraphQL
、JSON
、Markdown
等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。
1.安装 Prettier
shellnpm install prettier -D
2.配置 .prettierrc
文件:
none
,比如对象类型的最后一个属性后面是否加一个,;json{
"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
中的配置6.测试 Prettier
是否生效
在package.json中配置一个scripts:
json"prettier": "prettier --write ."
ESLint
shellnpm install eslint -D
ESLint
shellnpx eslint --init
第一步选择如何使用 ESLint
,选第二个
第二部模块化选择 ESModule
第三步选择框架,根据实际情况选择 React
第四步选择是否 TypeScript
,根据实际情况选择
第五步选择代码运行的环境,两个可以同时选择
第六步,配置文件类型, 选 js
第七步,根据你上面的选择询问你要不要安装包,我上面选了React和TypeScript
VSCode
需要安装 ESLint
插件:ESLint
和 Prettier
冲突的问题:安装插件:
shellnpm install eslint-plugin-prettier eslint-config-prettier -D
添加 Prettier
插件:plugin:prettier/recommended
js// .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'
}
}
}
JSON"eslint.alwaysShowStatus": true,
json"lint": "eslint ."
虽然现在已经要求项目使用 ESLint
了,但是不能保证组员提交代码之前都将 ESLint
中的问题解决掉了:
也就是希望保证代码仓库中的代码都是符合 ESLint
规范的;
那么需要在组员执行 git commit
命令的时候对其进行校验,如果不符合eslint规范,那么自动通过规范进行修复;
那么如何做到这一点呢?可以通过 Husky
工具:
husky
是一个 git hook
工具,可以帮助我们触发 git
提交的各个阶段:pre-commit
、commit-msg
、pre-push
如何使用 husky
呢?
这里我们可以使用自动配置命令:
shellnpx husky-init && npm install
这里会做三件事:
1.安装husky相关的依赖:
2.在项目目录下创建 .husky
文件夹:
3.在package.json中添加一个脚本:
4.接下来,我们需要去完成一个操作:在进行commit时,执行lint脚本:
这个时候我们执行 git commit
的时候会自动对代码进行 lint
校验。
通常我们的 git commit
会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制。
但是如果每次手动来编写这些是比较麻烦的事情,我们可以使用一个工具:Commitizen
Commitizen
是一个帮助我们编写规范 commit message
的工具;1.安装 Commitizen
shellnpm install commitizen -D
2.安装 cz-conventional-changelog
,并且初始化 cz-conventional-changelog
:
shellnpx commitizen init cz-conventional-changelog --save-dev --save-exact
这个命令会帮助我们安装cz-conventional-changelog:
并且在package.json中进行配置:
这个时候我们提交代码需要使用 npx cz
:
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 | 代码回退 |
我们也可以在scripts中构建一个命令来执行 cz:
如果我们按照 cz
来规范了提交风格,但是依然有同事通过 git commit
按照不规范的格式提交应该怎么办呢?
commitlint
来限制提交;1.安装 @commitlint/config-conventional 和 @commitlint/cli
shellnpm i @commitlint/config-conventional @commitlint/cli -D
2.在根目录创建commitlint.config.js文件,配置commitlint
jsmodule.exports = {
extends: ['@commitlint/config-conventional']
}
3.使用husky生成commit-msg文件,验证提交信息:
shellnpx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
对项目进行目录结构划分:
diff 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
tsximport React from 'react'
function App() {
return (
<div className="App">
<h1>React App</h1>
</div>
)
}
export default App
index.tsx
tsximport 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 />)
common.less
index.less
reset.less
DIFFsrc/assets/css
+ |- common.less 公共样式
+ |- index.less
+ |- reset.less 自定义重置样式
reset.less
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
less@import './reset.less';
@import './common.less';
normalize.css
包shellnpm install normalize.css
index.tsx
中引入 normalize.css
和 index.less
js...
import 'normalize.css'
import './src/assets/index.less'
...
为了方便开发,我们可以设置一份通用的 React
组件代码模板。
TSXimport 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)
vscode
中点击 文件->首选项->配置用户代码片段, 选择 typescriptreact.json
配置文件${1:Template}
复制进去,如下图所示,这样做的目的是当输入 tsreact
时,可以同时修改这三个变量名,而不用一个一个改了tsreact
即可创建模板react-router-dom
包shellnpm i react-router-dom
src/index.tsx
中导入 HashRouter
对App组件进行包裹tsximport { HashRouter } from 'react-router-dom'
root.render(
<HashRouter>
<App />
</HashRouter>
)
src/router/index.tsx
中配置路由映射表tsximport 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
中使用路由tsximport 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
react-redux
和 @reduxjs/toolkit
两个包shellnpm install react-redux @reduxjs/toolkit
src/store
中创建storestore/index.ts
tsimport { 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
tsimport { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
count: 0
},
reducers: {
incremented: (state) => {
state.count += 1
},
decremented: (state) => {
state.count -= 1
}
}
})
export const { incremented, decremented } = counterSlice.actions
export default counterSlice.reducer
src/index.tsx
中提供storetsximport { Provider } from 'react-redux'
import store from './store'
root.render(
<Provider store={store}>
<HashRouter>
<App />
</HashRouter>
</Provider>
)
获取并且显示 state
TSXimport { useAppSelector, shallowEqualApp, useAppDispatch } from './store'
import { incremented, decremented } from '@/store/modules/counter'
...
const { count } = useAppSelector(
(state) => ({
count: state.counter.count
}),
shallowEqualApp
)
const dispatch = useAppDispatch()
function addCount() {
dispatch(incremented())
}
function subCount() {
dispatch(decremented())
}
...
return (
...
<div>count:{count}</div>
<button onClick={subCount}>-1</button>
<button onClick={addCount}>+1</button>
...
)
修改 state
tsximport { useAppDispatch } from './store'
...
const dispatch = useAppDispatch()
function handleChangeMessage() {
dispatch(changeMessage('哈哈哈哈哈哈'))
}
...
return (
...
<button onClick={handleChangeMessage}>changeMessage</button>
...
)
在根目录下添加两个文件用以配置不同环境下的环境变量 .env.development
REACT_APP_BASE_URL = 'www.development.com'
.env.production
REACT_APP_BASE_URL = 'www.production.com'
同时需要在 react-app-env.d.ts 声明变量类型
ts/// <reference types="react-scripts" />
declare namespace NodeJS {
interface ProcessEnv {
readonly REACT_APP_BASE_URL: string
}
}
安装 axios
tsnpm i axios@0.27.2
修改 service
的目录结构为
DIFF+ |- /config
+ |- index.ts
+ |- /request
+ |- index.ts
+ |- types.ts
+ |- index.ts
/service/config.ts
tsconst BASE_URL = process.env.REACT_APP_BASE_URL
export const TIME_OUT = 1000
export { BASE_URL }
/service/request/index.ts
tsimport axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { RequestConfig } from './types'
// 拦截器: 蒙版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
tsimport 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
tsimport { BASE_URL, TIME_OUT } from './config'
import Request from './request'
const request = new Request({
baseURL: BASE_URL,
timeout: TIME_OUT
})
export default request
除了 styled-components本身之外还要安装它的类型声明
shellnpm i -D styled-components @types/styled-components
本文作者:叶继伟
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!