React17+React Hook+TS4 最佳实践仿 Jira 企业级项目笔记
前言
- 个人笔记,记录个人过程,如有不对,敬请指出
- React17+React Hook+TS4 最佳实践仿 Jira 企业级项目项目完成到第十章,剩下后面就没有看了,说的不是特别好
- husky方便我们管理git hooks的工具
REST-API风格
https://zhuanlan.zhihu.com/p/536437382
json-server
- 安装
1 | npm install -g json-server |
- 项目安装
1 | npm install -D json-server |
项目开始
用jsx渲染开发工程列表
- 初始化代码出现问题,表单收集的组件无法将请求结果发送给list组件
1 | import React, { useEffect, useState } from "react"; |
- 解决办法(3-2 用状态提升分享组件状态,完成工程列表页面),也就是将请求放在父组件当中
- 顺带一提,如果我们使用的是
vite
创建的react项目,就无法像老师一样直接使用process.env
来读取设置的变量了
学习自定义hook
- useHooks(不管是自带的hooks还是自己创建的hooks),不可以在普通函数中运行,只能在hooks当中使用或者其他hooks使用,所以在自定义hooks的时候,需要以useXxx开头
使用自定义的useMount和useDebounce
- useMount
1 | /*只在初次挂载执行*/ |
- useDebounce(防抖)
1 | /*自定义防抖hooks*/ |
- useDebounce的使用
1 | /*搜索参数*/ |
useDebounce的理解
传入: 传入需要节流的值和延迟
返回: 返回节流后的新state数据
原理:
- 内部对传入的value进行重新构建一个
state
,当传入的value发生改变的时候的时候,会被内部debounce创建的节流函数所捕捉,捕捉到后,如果中途没有重新捕捉到新的值,则会在设置的时间之后更新内部debounce的值,否则的话就会中断更新,重新计时
- 内部对传入的value进行重新构建一个
图示原理
js改造为ts
文件名更改
- js -> 改为 ts
- jsx -> 改为tsx
遇到qs模块types缺失的情况
: Could not find a declaration file for module 'qs'.xxxx,npm i --save-dev @types/qs
,安装对应的types即可
1 | yarn add @types/qs -D |
- 注意箭头函数和普通函数的泛型书写位置
1 | // 箭头函数 |
鸭子类型和json-server中间件
- 鸭子类型
- ts是只看是否实现了这个接口当中的成员,实现了就可以通过,没有实现就不通过,不管有没有定义
- 说通俗点就是只要你符合这个规定里面的规则,就是他说的一个东西
- 比如鸭子会嘎嘎叫,只要你会嘎嘎叫,ts就认为你是鸭子
1 | interface Base { |
- json-server中间件
- 注意POST为大写
- package.json更改
"json-server": "json-server __json_server_mock/db.json --watch --port 3033 --middlewares __json_server_mock/middleware.js"
1 | module.exports = (req,res,next) => { |
- 登录表单
1 | import React, { FormEvent } from "react"; |
安装jira-dev-tool
如果安装后项目启动数据库连接失败,可以试试运行这个命令
1 | npx msw init public |
使用自定义useHttp处理登录状态
关于
Parameters
的使用,可以看看ts官方示例https://www.typescriptlang.org/docs/handbook/utility-types.html所以老师当中的这个是什么意思呢?
1 | export const useHttp = () => { |
- 在使用函数的时候,如果是数组,想要将数组当中的数据依次传入数组,可以使用扩展运算符
1 | const arr1 = [1, -1, 0, 5, 3]; |
更改为antd
- 表格排序的知识
1 | string.localeCompare(targetString,locales,options); |
使用css-in-js-Emotion
- App.css样式更改为如下(App.css为全局样式)
1 | html{ |
- 安装emotion
1 | yarn add @emotion/react @emotion/styled |
安装编辑器插件
- webstorm:
Styled Components & Styled JSX
,不过最新版本的好像都自动安装了 - vscode:
vscode-styled-components
- webstorm:
使用emotion
1 | // html自带标签的使用 |
- 注意,不管有没有css样式后面,必须要接一个模板字符串,否者会报错
1 | //不报错 |
- 设置多个背景(学到了,学到了,)
background-image
属性用于为一个元素设置一个或者多个背景图像。background-position
属性为每一个背景图片设置初位置- 一个值为x,y设置相同的位置
- 二个值分别为x轴位置和y轴位置
background-size
属性设置背景图片大小- 一个值: 指定图片的宽度,高度为
auto
- 二个值: 分别指定图片的高度 和 宽度
- 逗号分割多个值,设置多重背景
- 一个值: 指定图片的宽度,高度为
1 | /*设置背景图*/ |
grid和flex各自的应用场景
- 一看空间
- 一般来说,一维布局用flex,二维布局用grid
- 二看内容和布局
- 从内容出发: 有一组内容(数量一般不固定),然后希望他们均匀分布在容器在当中,并且由内容自己的大小决定占据的空间(用flex)
- 从布局出发:先规划网格(数量一般比较固定),然后再把元素往里填充(用grid)
css-in-js:Row组件实现
- emotion允许我们像react一样传递参数来达到自定义样式的效果
- Row组件样式
- 当然,可以不写ts在这里,不过不写会有警告在tsx当中~
1 | import styled from "@emotion/styled"; |
- 使用
1 | import styled from "@emotion/styled"; |
完成项目列表页面样式
使用emotion的css
- 在react当中,我们可以直接对组件使用
style
设置样式,但是不支持一些子元素选择符,伪类等一些高级选择器的
1
<MyComponent style={{ marginBottom:'2rem' }}/>
- 所以我们可以使用
@emotion/react
来代替我们
1
2
3
4
5/** @jsx jsx */
import { jsx } from "@emotion/react";
<Form css={{marginBottom: '2rem'}} >
</Form/>- 老师是这样子写的,但是我报错了
pragma and pragmaFrag cannot be set when runtime is automatic.
,不知道为什么,就这样子吧,后面遇到再说
- 在react当中,我们可以直接对组件使用
图片以svg形式渲染
- 我们如果直接在React使用img去使用svg图片的时候,并不能去设置svg参数了
1 | import Logo from "../src/assets/svg/software-logo.svg"; |
所以我们应该使用如下方式去使用svg图片(通过组件的形式),这样子我们就可以设置svg的一些参数了
1 | import { ReactComponent as SoftwareLogo } from "../src/assets/svg/software-logo.svg"; |
清除警告todo
The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address a....
- 原因:a标签没有href导致的,必须要一个合法的跳转链接,不可以
#
,也不可以javascript:;
- 解决:替换为button,或者使用组件库,设置button的属性为
link
- 原因:a标签没有href导致的,必须要一个合法的跳转链接,不可以
React Hook useEffect has a missing dependency: 'callback'. Either include it or remove the dependency array.
- 原因:依赖项没有加入在依懒收集里面导致报错
- 解决:
1 | useEffect(() => { |
- 不要乱用object
- 覆盖范围很广,比如一个箭头函数变量,ts都认为这个是object
1 | export const isVoid = (value:unknown) => value === undefined || value == null || value === ''; |
Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported.
(我这边失败了,这个具体的就不加了)- 原因:jira-dev-tool问题
- 解决:安装jira-dev-tool@next
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32yarn add jira-dev-tool@next
App.tsc
- import { loadDevTools } from 'jira-dev-tool';
+ import { loadServer,DevTools } from 'jira-dev-tool';
//原来写法
loadDevTools(() => {
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById('root')
);
});
//更改为
loadServer(() => {
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<DevTools/>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById('root')
);
});
登录注册页面loading和error状态处理
需要注意的是try-catch是同步的
为什么不能用useAsync的error,因为error更新是异步的,try-catch是同步的
1 | const {run,isLoading,error} = useAsync(undefined,{throwOnError: true}); |
未捕获错误(Uncaught Errors)-错误边界
react官网对于错误边界的说明
只有 class 组件才可以成为错误边界组件
react-error-boundary
错误边界无法捕获以下场景中产生的错误:
1 | 事件处理 |
老师创建的组件的要求
- 1.可以自定义错误显示的组件
- 2.可以展示子组件当没有错误的时候
代码
1 | import React, {Component, ErrorInfo, ReactNode} from "react"; |
使用useRef实现useDocumentTitle
需求
- 每一个组件可以使用
useDocumentTitle
方法,第一个参数传入新标题,第二个标题传入卸载组件后是否复原
- 每一个组件可以使用
疑问
- 为什么useDocumentTitle当中的设置
document.title
要放置在useEffect
1
2
3
4
5
6
7因为在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
说通俗点就是useEffect可以放置一些副作用操作在里面,并设置为依赖.而我们的document.title就是一个副作用,所以需要放置在useEffect
别忘记了`effect`单词本身的意思哦- useRef为什么要使用
- 为什么useDocumentTitle当中的设置
1 | /* 网页标题更改 */ |
动态改变网页标签显示的标题#todo
- 第一种
- react-helmet
- 第二种
添加项目列表和项目详情
react-router-dom和react-router的关系
- react-router管理路由关系,计算结果由react-router-dom来消费使用
为什么Link是从react-router-dom引入的
- 因为Link会被渲染为一个a标签,并且需要处理点击事件,和浏览器是相关联的
路由表和路由的书写技巧
- 不可以写斜杆,写斜杆代表不管你从哪里开始,我都是从
/
开始的,
所以我们需要在不破坏当前路由的基础上把路由加进去,所以不能有/
/
带了斜杆就是根了(也就url的路径从我开始算起)./
在不破坏当前路径下载后面添加内容,也可以不写./
- 于是乎下面三个效果都是一样的,最终匹配的都是/home/message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//路由表
{
path:'/home',
element:<Home/>
children:[
path:"./message",
path:'/home/message',
path:'message',
]
}
//路由链接的to的书写也和这个相同,
//下面二个均是在对应url后面添加相应的内容
//比如之前url为/home,那么点击`message组件显示`,
//那么url就会变为/home/message
<NavLink to="message">Message组件显示</NavLink>
<NavLink to="news">News组件显示</NavLink>- 不可以写斜杆,写斜杆代表不管你从哪里开始,我都是从
一个很奇怪的问题
- 必须要转化为字符串才可以
1 | return <Link to={String(project.id)}>{ project.name } </Link> |
- 注意Navigate写法
1 | <h1>ProjectScreen</h1> |
useSearchParams初步完成
- 可以用来获取search参数(也就是url后面的这种参数
/a?id=4&age=14
) - 返回一个URLSearchParams,
- 所以要读取某一个值就需要使用
get
方法
1 | //url |
问题
- 为什么没有
as const
会出现下图问题
1
2
3
4
5
6
7
8
9
10import {useSearchParams} from "react-router-dom";
export const useUrlQueryParams = (keys:string[]) => {
const [searchParams] = useSearchParams();
return [
keys.reduce((pre,key) => {
return {...pre,[key]:searchParams.get(key) ?? ''}
},{} ),
searchParams,
]
}- 先来看一个小例子
- 想一想a的类型是什么
1
const a = ['jack',12,{gender:'男'}]
- 原因很简单,因为ts认为数组都是相同的,使用为了确保数组当中都有相同的,所以就使用了多个
|
- 所以我们return后面添加一个
as const
即可
1
2
3
4
5
6
7
8
9
10export const useUrlQueryParams = (keys:string[]) => {
const [searchParams] = useSearchParams();
return [
keys.reduce((pre,key) => {
return {...pre,[key]:searchParams.get(key) ?? ''}
},{} ),
searchParams,
] as const
}- 为什么没有
不过这还不够,我们点入reduce的ts声明可以看到,reduce当中
previousValue
返回值依赖于初始化时候传入的泛型,所以我们可以指明initialValue在reduce当中
1 | reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; |
- 最终初步完成如下
1 | export const useUrlQueryParams = (keys:string[]) => { |
useSearchParams完成(使用useMemo解决)
上一次完成的方法我们使用发现会无限渲染我们可以借助
why-did-you-render
来检测是什么造成了页面渲染通过排查,useDebounce当中的useEffect发现依赖项目变化了,进而去重新渲染页面,但是params在每次渲染的时候都是一个新的,导致useEffect又认为发生了变化,进而重复无限渲染
- 所以我们使用
useEffect
的时候,我们应该将基本类型和组件状态(使用useState)放置到依赖里面,非组件状态的对象,绝对不可以放在依赖里面!! - 所以检查我们可以使用why-did-you-render
- 所以我们使用
1 | const [params] = useUrlQueryParams(['name','age']) |
- 所以我们循环遍历search参数的时候,返回的是一个对象,又因为react只对对象进行地址比较,所以就导致每次重新渲染返回的对象不同,所以就造成了重复渲染,解决后代码如下(使用
useMemo
)
1 | export const useUrlQueryParams = <K extends string>(keys:K[]) => { |
完成的URL状态管理代码
1 | export const useUrlQueryParams = <K extends string>(keys:K[]) => { |
实现Id-Select解决Id难题
获取AntDesign组件当中属性的二种方式
- 方法1:通过
React.ComponentProps
;
1
2
3
4import React from "react";
import {Select} from "antd";
type SelectProps = React.ComponentProps<typeof Select>;- 方法2:Ctrl按照进入组件,然后找到后复制粘贴就可以引入
1
import {SelectProps} from "antd/es/select";
- 方法1:通过
用useEditProject编辑项目
柯里化
- 因为有一个参数实现已经知道了,后一个参数需要等待才可以知道,就可以采用这种方式
1 | const { mutate } = useEditProject(); |
惰性初始化和使用useRef保存函数和useState保存函数的方法
什么是惰性初始化
- 惰性初始化的时候(也就是传入一个函数就是惰性初始),此函数会被立即执行,并将返回的值作为返回数组的第一个参数,其他和普通state是相同的(调用setState也会触发页面重新渲染)
- 惰性初始代码
1 | const [state,setState] = useState(() => { |
- 非惰性初始(也就是普通的state,传入的不是一个函数,)
1 | const [state,setState] = useState(someExpensiveComputation(props)); |
- 所以当我们想通过useState保存函数的时候,就不可以了,我们可以使用
useRef
来保存函数
useRef
- 我觉得我快忘记了,再看看React官网的描述吧
- 比较重点就是ref对象发生变化,并不会引发组件的重新渲染,useRef的值是保存在一个
.current
属性当中
- 比较重点就是ref对象发生变化,并不会引发组件的重新渲染,useRef的值是保存在一个
- 老师在最后说的一个问题,将代码改为下面样子,我们点击
设置callBack
后,再点击执行callBack-设置后不正常输出'init'
按钮,发现输出的却是init
- 这是因为组件在编译渲染完成后,
执行callBack-设置后不正常输出'init'
按钮始终指向初次时候的函数地址,又因为useRef即使被更新也不会被重新渲染,导致此按钮指向的依旧是初始化时候的函数 - 而为什么
执行callBack-设置后正常输出'updated'
按钮却是正常输出,因为传入的是高阶函数,在执行的时候才会寻找current
所指向的函数去执行,所以执行就没有问题
- 这是因为组件在编译渲染完成后,
1 | import * as React from 'react' |
useState保存函数的方法
- 既然存在惰性初始化,我们就让他执行就可以,我们返回一个函数就可以啦
1 | const [retry,setRetry] = useState(() => () => { |
优化异步请求
出现的情况
- 在外面等待数据返回的时候,突然退出登录,然后未中断请求,导致
setData
等操作会出现异常(我练习的时候好像没有,不过也学习下)
- 在外面等待数据返回的时候,突然退出登录,然后未中断请求,导致
解决异步的时候退出的问题
1 | //1.建立useMountedRef,并设置状态 |
- 当使用
useCallback
,或者useMemo
的时候,可以里面会有依赖state,然后我们会将依赖加入进去,但是加入后,会发生无限循环的问题,这个时候我们就可以使用setData
的第二种形式了,然后结合useCallback
或者useMemo
1 | //第一种(造成无限循环) |
什么时候使用useMemo,useCallback
- 非基本类型想做依赖,就需要使用这二个
- 比如在想自定义hooks的时候,返回了函数,或者非基本数据类型,或者并没有被useState包裹的数据的时候,就需要使用这二个了
状态提升
- const其实变量提升(依照var的变量提升,我们可以想一想组件的状态提升)
目前我们为了使用一个能被多个组件调用的方法或者是属性(可以称其为”全局方法或属性“),我们将其提升到共同的父组件当中,但是当子组件需要使用全局方法或属性的时候,父组件和要使用的子组件只有一层还好说,当有多层的时候,就会出现父传给A组件,A组件传递给B组件,B组件在传给C组件,C组件再来使用,来看看下面集中方法
第一种: 放在全局状态,通过一层一层传递,
第二种: 还有一种Context的方法
不适用content传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
// 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
// 因为必须将这个值层层传递所有组件。
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}- 使用
createContext
传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}第三种: 组合组件(component composition)
- 其实就是将组件传递(传递jsx) - 缺点是没有解决向下钻的问题,但是方法不需要传递了,(定义和调用很近),示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 使用component composition
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
// 现在,我们有这样的组件:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout userLink={...} />
// ... 渲染出 ...
<NavigationBar userLink={...} />
// ... 渲染出 ...
{props.userLink}
//未使用component composition之前
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
useUndo
- 未使用
useReducer
的写法
1 | import {useCallback, useState} from "react"; |
- 使用
useReducer
的写法,仅供参考
1 | import {useCallback, useReducer, useState} from "react"; |
- 顺带一提,其实
useReudcer
里面的action,其实什么都可以,只是我们用的多的都是带有type
的而已
redux
redux用在哪里都可以,react-redux连接redux.如果redux不使用在react,就可以不适用react-redux
redux作用就是用现在的
state
,产生下一个state
当redux发生什么事情的时候,就会戳一下
- redux发生
dispatch
,触发subscribe
- dispatch -> counter -> store
- redux发生
redux保持一个同步之前学习的,为什么呢?保持纯洁性,因为如果是异步请求了,就不是可预测了的
- 副作用,对现实世界产生影响
- 比如发送请求,修改全局变量
- 副作用,对现实世界产生影响
redux怎么知道要更新数据呢?怎么知道要执行
订阅
的内容呢?- 判断前一次的数据是否和后一次的数据相同,相同就不更新,不相同就不更新
- 这样子比较 变量 a === 变量b
redux-thunk在redux里面处理异步流行的一个库(注意,redux可以进行异步操作,但是redux-thunk可以帮助我们隐藏异步实现的细节)
可以看到,组件内部并不想知道怎么请求的,(具体异步细节忽略)
reduxjs/tooltik和react-redux
- 安装依赖
1 | yarn add react-redux @reduxjs/tooltik |
有关reduxjs/toolkit部分
- 书写片段
- project-list.slice.ts
1 | import {createSlice} from "@reduxjs/toolkit"; |
- 主入口
- index.tsx
1 | import {configureStore} from "@reduxjs/toolkit"; |
有关react-redux
1 | import {AuthProvider} from "./authContext"; |
使用
- 获取设置的state参数
const { useSelector } from "react-redux"
1 | import {useSelector} from "react-redux" |
- 调用设置的方法
const { useDispatch } from "react-redux"
1 | const { useDispatch } from "react-redux"; |
- 这里面的
projectListSliceActions
对应下面暴露出来的projectListSliceActions
1 | import {createSlice} from "@reduxjs/toolkit"; |
执行异步
- 具体可看这篇文章,这里做个记录~
方法1:
在home.js中, 通过createAsyncThunk函数创建一个异步的action
再在extraReducers中监听这个异步的action的状态, 当他处于fulfilled状态时, 获取到网络请求的数据, 并修改原来state中的数据
1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit" |
- 其他地方引入执行异步
1 | import { useDispatch } from "react-redux"; |
方法2
1 | 如果我们不想通过在extraReducers在监听状态, 再修改state这种方法的话, 还有另外的一种做法 |
1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit" |
- 其他地方引入执行异步
1 | import { useDispatch } from "react-redux"; |
总结
- 先看看片段
- projectList.slice.ts
1 | import {createSlice} from "@reduxjs/toolkit"; |
- store/index.ts
1 | import {configureStore} from "@reduxjs/toolkit"; |
获取数据使用
useSelector (import {useSelector} from "react-redux")
1
2
3
4
5import { useSelector } from "react-redux"
const selectProjectModalOpen = state => state.projectList.projectModalOpen;
const showModal = useSelector(selectProjectModalOpen);
//等同于 const showModal = useSelector((state) => state.projectList.projectModalOpen)触发方法使用
useDispatch ( import { useDispatch } from "react-redux" )
1
2
3
4
5
6import { useDispatch } from "react-redux"
import {projectListSliceActions} = "./projectList.slice";
<button onClick={() => dispatch(projectListSliceActions.closeProjectModal())}>点击我关闭</button>
用代码分割优化性能
- react官网说明
- 使用
React.lazy
- 需要配合
Suspense
组件使用,传入fallback
参数作为加载时候的画面 - 注意组件要使用默认导出
- 需要配合
1 | import React,{lazy,Suspense} from 'react'; |
使用React.memo
- 使用后,只有当组件的props或者全局状态,比如redux发生变化的时候,才会执行重新渲染
Profiler
生产环境禁止使用
- 如果需要,在编译的时候添加(如果是create react app)创建的话
1
2yarn build --profile
npm run build --profile
单元测试(React)
我们
setupWorker
以前曾为开发创建了一个假服务器。现在我们使用不同的函数,setupServer
因为测试将在 Node.js 环境中运行,而不是在实际的浏览器环境中。只有浏览器环境具有 Service Worker 功能,因此我们在测试中使用 MSW 的方式实际上并不涉及 Service Worker。- 也就是说
setupServer
在node环境下使用的,不涉及到浏览器,setupWorker
在浏览器中运行的
- 也就是说
最基础的单元测试
- 随便哪里建立一个sum函数
1
2
3export function sum(a, b) {
return a + b;
}- src/tests/sun.ts文件(其实你取名叫
sun.test.ts
也可以,其实都会识别)
1
2
3
4
5import {sum} from "../utils";
test('测试结果是否为100',() => {
expect(sum(50,50)).toBe(100)
})然后运行
yarn test
- 可以看到自动去寻找了
__tests__
当中的文件进行测试
- 可以看到自动去寻找了
先看看msw拦截异步请求的代码怎么写
总的来说就是: 创建handler,使用handler,开始拦截
1.创建handler
1 | import {rest} from "msw"; |
- 2.使用handler
1 | import { setupWorker,SetupWorker } from "msw"; |
- 3.开始拦截(只要匹配到了handler当中的列表,就进行拦截)
- 我访问
/login
或者/list
就会被拦截,从而返回假数据
- 我访问
1 | import { worker } from "./mocks/browser" |
- 演示地址
setupServer和setupWorker
- 也就是说
setupServer
在node环境下使用的,不涉及到浏览器,setupWorker
在浏览器中运行的 - 老师的代码没有过多解释,我们就以下面代码做个简单说明
- 其实老师大概步骤也和上面msw拦截异步请求代码一样
- 先监听,然后拦截,之后才是创建
1 | import {setupServer} from "msw/node"; |
- 其实很简单,
server.use
理解为向监视器里面添加东西,添加的东西会被拦截并做处理,只不过我们每次测试完毕,都将里面的handler进行了清空
额外小知识
encodeURIComponent(转义URL当中部分字符的)
encodeURI(转移整个URL内容的)
.env
,.env.development
- 当为
npm start
的时候.webpack会去读取.env.development
文件 - 当为
npm run build
编译之后,webpack会去读取.env.development
文件 - 并将读取的文件作为一个对象存储在
process.env
当中 - 比如
.env.development
有如下内容
1
abc = 'www.baidu.com'
- 那么我们就可以读取
1
process.env.abc
- 如果是vite读取,则需要通过如下来读取,并且设置的变量必须要以
VITE_
开头
1
const apiUrl = import.meta.env
- 当为
遇到这种不明确的情况(比如name=),到底是搜索名字为空的,还是要忽略这种情况呢?所以我们需要在前端做处理
TS7016: Could not find a declaration file for module './screens/projectList'. 'D:/develop/phpstudy_pro/WWW/React-cli/react_17_projectJira/src/screens/projectList/index.jsx' implicitly has an 'any' type.
这种报错- 就是缺少声明文件,要么自己添加对应的
xxx.d.ts
文件或者使用//@ts-ignore
进行忽略
- 就是缺少声明文件,要么自己添加对应的
useHooks(不管是自带的hooks还是自己创建的hooks),不可以在普通函数中运行,只能在hooks当中使用或者其他hooks使用,所以在自定义hooks的时候,需要以useXxx开头