2022-12-02
React
00

目录

前言
1. 通过 Create-React-App 创建项目
2. 配置 CRACO
3. 集成 EditorConfig 配置
4. 使用 Prettier 工具
5. 使用 ESLint 检测
6. Git Husky和 ESLint (可选)
7. Git Commit 规范 (可选)
7.1 代码提交风格
7.2 代码提交验证
8. 文件目录结构划分
9. CSS 重置
10. 设置代码片段
11. 配置 React-Router
12. 配置 Redux 状态管理
13. 环境配置
14. axios 网络请求封装
15. 引入 styled-components

前言

本章的源码可以通过脚手架 jw-cli 下载

sh
npm i @yejiwei/cli -g

image.png

image.png

image.png

接着执行以下命令即可

cd react-app npm install npm start

1. 通过 Create-React-App 创建项目

  1. 创建一个 TypeScript 模版的 React 项目
shell
npx create-react-app react-app --template typescript
  1. 运行项目
shell
cd react-app npm start
  1. 输入 localhost:3000 显示如下如即成功

image.png

2. 配置 CRACO

CRACO 全称 Create React App Configuration Override,取首字母即组成了工具名称。是为了无 eject 、可配置式的去修改 CRA 默认提供的工程配置,这样既能享受 CRA 带来的便利和后续升级,也能自己去自定义打包配置完成项目需要,一举两得。

  1. 从npm安装最新版本的包作为开发依赖项:
shell
npm i -D @craco/craco
  1. 在项目的根目录中创建一个CRACO配置文件并配置:
diff
react-app ├── node_modules + ├── craco.config.js └── package.json
  1. 更新 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" }
  1. 支持 TypeScript ,使用 CRACO 提供的类型包
shell
npm i -D @craco/types
  1. craco.config.js 配置

因为不同的项目有不同的需求和业务,配置文件也会不同,根据自己需求配置即可,遇到问题可到找craco官方文档 查看

下面我以配置 less 和别名为例:

安装craco-less

shell
npm install -D craco-less

如果上面 craco-less 因为版本原因报错,在命令后面加 @alpha

配置craco-less插件和别名

js
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"), }, }, };
  1. tsconfig.jsoncompilerOptions 添加配置
json
"baseUrl": ".", "paths": { "@/*": ["src/*"] }
  1. 运行 npm run start ,项目能正常跑起来就OK。

3. 集成 EditorConfig 配置

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

image.png

4. 使用 Prettier 工具

Prettier 是一款强大的代码格式化工具,支持 JavaScriptTypeScriptCSSSCSSLessJSXAngularVueGraphQLJSONMarkdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

1.安装 Prettier

shell
npm install prettier -D

2.配置 .prettierrc 文件:

  • useTabs:使用tab缩进还是空格缩进,选择false;
  • tabWidth:tab是空格的情况下,是几个空格,选择2个;
  • printWidth:当行字符的长度,推荐80,也有人喜欢100或者120;
  • singleQuote:使用单引号还是双引号,选择true,使用单引号;
  • trailingComma:在多行输入的尾逗号是否添加,设置为 none,比如对象类型的最后一个属性后面是否加一个,;
  • semi:语句末尾是否要加分号,默认值true,选择false表示不加;
json
{ "useTabs": false, "tabWidth": 2, "printWidth": 80, "singleQuote": true, "trailingComma": "none", "semi": false }

3.创建 .prettierignore 忽略文件

/dist/* .local .output.js /node_modules/** **/*.svg **/*.sh /public/*
  1. VSCode 需要安装 Prettier 的插件

image.png

  1. VSCode 中的配置
  • settings =>format on save => 勾选上

image.png

  • settings => editor default format => 选择 prettier

image.png

6.测试 Prettier 是否生效

  • 测试一:在代码中保存代码;
  • 测试二:配置一次性修改的命令;

在package.json中配置一个scripts:

json
"prettier": "prettier --write ."

5. 使用 ESLint 检测

  1. 安装 ESLint
shell
npm install eslint -D
  1. 配置 ESLint
shell
npx eslint --init

第一步选择如何使用 ESLint ,选第二个

image.png

第二部模块化选择 ESModule

image.png

第三步选择框架,根据实际情况选择 React

image.png

第四步选择是否 TypeScript ,根据实际情况选择

image.png

第五步选择代码运行的环境,两个可以同时选择

image.png

第六步,配置文件类型, 选 js

image.png

第七步,根据你上面的选择询问你要不要安装包,我上面选了React和TypeScript

image.png

  1. VSCode 需要安装 ESLint 插件:

image.png

  1. 解决 ESLintPrettier 冲突的问题:

安装插件:

shell
npm 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' } } }
  1. VSCode中eslint的配置
JSON
"eslint.alwaysShowStatus": true,
  1. 在package.json中配置一个scripts:
json
"lint": "eslint ."

6. Git Husky和 ESLint (可选)

虽然现在已经要求项目使用 ESLint 了,但是不能保证组员提交代码之前都将 ESLint 中的问题解决掉了:

  • 也就是希望保证代码仓库中的代码都是符合 ESLint 规范的;

  • 那么需要在组员执行 git commit 命令的时候对其进行校验,如果不符合eslint规范,那么自动通过规范进行修复;

那么如何做到这一点呢?可以通过 Husky 工具:

  • husky 是一个 git hook 工具,可以帮助我们触发 git 提交的各个阶段:pre-commitcommit-msgpre-push

如何使用 husky 呢?

这里我们可以使用自动配置命令:

shell
npx husky-init && npm install

这里会做三件事:

1.安装husky相关的依赖:

image.png

2.在项目目录下创建 .husky 文件夹:

image.png

3.在package.json中添加一个脚本:

image.png

4.接下来,我们需要去完成一个操作:在进行commit时,执行lint脚本:

image.png

这个时候我们执行 git commit 的时候会自动对代码进行 lint 校验。

7. Git Commit 规范 (可选)

7.1 代码提交风格

通常我们的 git commit 会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制。

image.png

但是如果每次手动来编写这些是比较麻烦的事情,我们可以使用一个工具:Commitizen

  • Commitizen 是一个帮助我们编写规范 commit message 的工具;

1.安装 Commitizen

shell
npm install commitizen -D

2.安装 cz-conventional-changelog,并且初始化 cz-conventional-changelog

shell
npx commitizen init cz-conventional-changelog --save-dev --save-exact

这个命令会帮助我们安装cz-conventional-changelog:

image.png

并且在package.json中进行配置:

image.png

这个时候我们提交代码需要使用 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代码回退
  • 第二步选择本次修改的范围(作用域)

image.png

  • 第三步选择提交的信息

image.png

  • 第四步提交详细的描述信息

image.png

  • 第五步是否是一次重大的更改

image.png

  • 第六步是否影响某个open issue

image.png

我们也可以在scripts中构建一个命令来执行 cz:

image.png

7.2 代码提交验证

如果我们按照 cz 来规范了提交风格,但是依然有同事通过 git commit 按照不规范的格式提交应该怎么办呢?

  • 我们可以通过 commitlint 来限制提交;

1.安装 @commitlint/config-conventional 和 @commitlint/cli

shell
npm i @commitlint/config-conventional @commitlint/cli -D

2.在根目录创建commitlint.config.js文件,配置commitlint

js
module.exports = { extends: ['@commitlint/config-conventional'] }

3.使用husky生成commit-msg文件,验证提交信息:

shell
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

8. 文件目录结构划分

对项目进行目录结构划分:

image.png

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

tsx
import React from 'react' function App() { return ( <div className="App"> <h1>React App</h1> </div> ) } export default App

index.tsx

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 重置

  1. 添加 common.less index.less reset.less
DIFF
src/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';
  1. 安装 normalize.css
shell
npm install normalize.css
  1. index.tsx 中引入 normalize.cssindex.less
js
... import 'normalize.css' import './src/assets/index.less' ...

10. 设置代码片段

为了方便开发,我们可以设置一份通用的 React 组件代码模板。

  1. 创建一份自己常用的模板
TSX
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)
  1. 复制到 snippet-generator网站 ,生成对应vscode的配置

image.png

  1. vscode 中点击 文件->首选项->配置用户代码片段, 选择 typescriptreact.json 配置文件

image.png

  1. 将生成的代码 变量名修改为 ${1:Template} 复制进去,如下图所示,这样做的目的是当输入 tsreact 时,可以同时修改这三个变量名,而不用一个一个改了

image.png

  1. 之后只要在文件中输入 tsreact 即可创建模板

2.gif

11. 配置 React-Router

  1. 安装 react-router-dom
shell
npm i react-router-dom
  1. src/index.tsx 中导入 HashRouter 对App组件进行包裹
tsx
import { HashRouter } from 'react-router-dom' root.render( <HashRouter> <App /> </HashRouter> )
  1. src/router/index.tsx 中配置路由映射表
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
  1. App.tsx 中使用路由
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 状态管理

  1. 安装 react-redux@reduxjs/toolkit 两个包
shell
npm install react-redux @reduxjs/toolkit
  1. src/store 中创建store

store/index.ts

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

ts
import { 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
  1. src/index.tsx 中提供store
tsx
import { Provider } from 'react-redux' import store from './store' root.render( <Provider store={store}> <HashRouter> <App /> </HashRouter> </Provider> )
  1. 在 App.tsx 测试使用

获取并且显示 state

TSX
import { 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

tsx
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 声明变量类型

ts
/// <reference types="react-scripts" /> declare namespace NodeJS { interface ProcessEnv { readonly REACT_APP_BASE_URL: string } }

14. axios 网络请求封装

安装 axios

ts
npm i axios@0.27.2

修改 service 的目录结构为

DIFF
+ |- /config + |- index.ts + |- /request + |- index.ts + |- types.ts + |- index.ts

/service/config.ts

ts
const BASE_URL = process.env.REACT_APP_BASE_URL export const TIME_OUT = 1000 export { BASE_URL }

/service/request/index.ts

ts
import 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

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

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本身之外还要安装它的类型声明

shell
npm i -D styled-components @types/styled-components
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:叶继伟

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!