}
export default A
```
## 副作用Effect
有部分逻辑直接写在函数体中,会影响组件的渲染,这部分代码会产生"副作用"。(例如修改 state 写到组件中,可能导致死循环渲染)
### React.StrictMode
React 的严格模式,在开发模式下,会主动重复调用一些函数,以使副作用凸显。所以在开发模式且开启严格模式,这些函数会被调用两次:
* 函数组件的函数体
* 参数为函数的`setState`
* 参数为函数的`useState`、`useMemo`、`useReducer`
### 重渲染案例
```jsx
import { useState } from 'react'
import B from './components/B.jsx'
function App() {
console.log('重渲染')
const [count, setCount] = useState(0)
// setCount(0)
// 直接在函数体中调用setState, Too many re-renders
/** 为什么初始值和新值一样,还会触发重渲染?
* setState()的执行流程(函数组件)
* setCount() --> dispatchSetData()
* 会先判断当前组件处于什么阶段
* 1. 渲染阶段: 不会检查state值是否相同
* 2. 非渲染阶段: 会检查state是否相同
* 2.1 不相同, 进行渲染
* 2.3 相同,不渲染
*
* */
/** 为什么初始值0, 第二次点按钮会重渲染, 第三次才不会重渲染
* 0 ----> 1 ----> 1 --> 1
* 打印 打印
* 非渲染阶段值相同: 在一些情况下会继续执行当前组件的重渲染, 但不会触发子组件的渲染, 且这次渲染不会产生实际的效果(通常发生在第一次相同)
*
*/
return (
)
}
export default App
```
```jsx
import React from 'react';
function B(props) {
console.log('B组件重新渲染');
return (
我是子组件
);
}
export default B;
```
### useEffect
> 将会产生副作用的函数写在useEffect的回调函数里
```js
useEffect(() => setCount(0))
/** useEffect(fn, [])
* fn作为第一个参数的函数将在组件渲染完成后执行
* 第二个可选参数是依赖项,只有依赖项发生变化,才执行. 数组中包含了**所有**外部作用域中会发生变化且在 effect 中使用的变量
* useState创建的setState在每次渲染获取都是相同的,写不写没关系
* 如果传空数组[], effect只会在初始化时触发一次
*/
```
### 清除 effect
```js
useEffect(() => {
// 防抖
const timeId = setTimeout(() => {
props.onSearch(keyword)
}, 1000)
return () => {
// 下次effect执行, 可以用来清理上次effect的影响
clearTimeout(timeId)
}
}, [keyword])
// return 返回一个清除函数, 在下次effect先执行
```
## useReducer
> useState 的替代方案
>
> 当 state 过于复杂时, 使用 useReducer 进行整合
```js
/** useReducer(reducer, initialArg, init)
* reducer: 整合函数
* 对state的所有操作都应该在该函数中定义
* 该函数的返回值, 会成为state的新值
* reducer执行时会收到两个参数:
* 第一个参数: 当前state最新的值
* 第二个参数: action: 对象, 从countDispatch 传进来的值
* 为了避免每次渲染都重新创建reducer,一般写在函数组件外面
* initialArg: state的初始值, 作用和uueState()中的值一样
* init: 函数, 可选参数, 惰性初始化, 初始state就由它决定,而不再是第二个参数
* @return
* 数组:
* 第一个参数: state
* 第二个参数: state 修改的派发器
* 具体通过reducer执行
*/
```
```jsx
import { useState, useReducer } from 'react'
import './App.css'
const initCounter = () => {
return 10
}
const countReducer = (state, action) => {
switch (action.type) {
case 'add':
return state + 1
case 'sub':
return state - 1
default:
return state
}
}
function App() {
// useState的写法
// const [count, setCount] = useState(0)
// const addHandler = ()=>{
// setCount(prev=>prev+1)
// }
// const subHandler = ()=>{
// setCount(prev=>prev-1)
// }
// 改用useReducer
// const [count,countDispatch] = useReducer(countReducer, 1)
const [count, countDispatch] = useReducer(countReducer, 1, initCounter)
const addHandler = () => {
countDispatch({ type: 'add' })
}
const subHandler = () => {
countDispatch({ type: 'sub' })
}
return (
)
}
export default App
```
## React.memo
React 组件会在两种情况下重新渲染
1. 组件自身的 state 发生变化
2. 组件的父组件重新渲染
如果子组件的 state 并没有发生变化, 但父组件重新渲染导致子组件也重新渲染, 为了减少性能损失, 可以用`React.memo()`
```js
/**
* React.memo()是高阶组件
* 接收另一个组件作为参数,返回一个包装后的新组件
* 包装后的新组件具有缓存功能
* 只有组件的props发生变化,才会触发组件的重新渲染,否则总是返回缓存中的结果
*/
```
```jsx
import { useState } from 'react'
import './App.css'
import B from './components/B'
import C from './components/C'
function App() {
console.log('A组件重渲染')
const [count, setCount] = useState(0)
return (
)
}
export default App
```
```jsx
import React from 'react'
const B = () => {
console.log('B组件重渲染')
s
return
B组件
}
export default React.memo(B)
```
```jsx
import React from 'react'
const C = props => {
console.log('C组件重新渲染')
return
props传过来的count: {props.count}
}
export default React.memo(C)
```
## useCalkback
```js
/**
* useCallback()
* 钩子函数,用来创建React中的回调函数
* 创建的回调函数可以在组件重新渲染时不重新创建
* 参数:
* 1.回调函数
* 2.依赖数组
* - 当依赖数组中的变量发生变化时,回调函数才会重新创建
* - 如果不指定依赖数组(不传),回调函数每次都会重新创建
* - 一定要把回调函数用到的变量都放到数组里, 除了setState
* 不放的话,变量的作用域只会停留在第一次创建的时候
*/
```
```jsx
import { useCallback } from 'react'
import { useState } from 'react'
import './App.css'
import B from './components/B'
import C from './components/C'
import D from './components/D'
import E from './components/E'
function App() {
console.log('A组件重渲染')
const [count, setCount] = useState(0)
const [num, setNum] = useState(1)
const addCount = () => {
setCount(prev => prev + 1)
}
/**
* useCallback()
*/
const addCount2 = useCallback(() => {
setCount(prev => prev + 1)
}, [])
const cAddN = useCallback(() => {
setCount(prev => prev + num)
setNum(prev => prev + 1)
}, [])
/**
* 没有收集num,num固定是第一次的1
*/
const cAddN2 = useCallback(() => {
setCount(prev => prev + num)
setNum(prev => prev + 1)
}, [num])
/**
* num放到依赖数组,num的作用域会跟着变化
*/
return (
count is {count}
num is {num}
)
}
export default App
```
```jsx
import React from 'react'
const B = props => {
console.log('B组件重渲染')
return (
B组件
)
}
export default React.memo(B)
/**
* B组件也重新渲染: props里的addCount在A组件重新渲染后重新创建
*/
```
```jsx
import React from 'react'
const C = props => {
console.log('C组件重渲染')
return (
C组件
回调使用useCallback
)
}
export default React.memo(C)
```
```jsx
import React from 'react'
const D = props => {
console.log('D组件重渲染')
return (
D组件
变量没有放到依赖数组
)
}
export default React.memo(D)
```
```jsx
import React from 'react'
const E = props => {
console.log('E组件重渲染')
return (
E组件
变量放到依赖数组
)
}
export default React.memo(E)
```
## hooks
> React 中的钩子只能在函数组件或者自定义钩子中调用
>
> 自定义钩子: 本质就是普通函数, 命名上以 use 开头
例子: useFetch
```js
import {useState} from "react"
const baseURL = import.meta.env.VITE_HOST
const useFetch = ({url,type='GET',headers,successCallback,errorCallback}) => {
const [loading, setLoading] = useState(false)
const fetchInit = {
method: type,
headers: headers || {
'Content-Type': 'application/json',
}
}
const fetchData = async (body)=>{
if (loading) {
console.log('等待服务器响应,请勿重复提交!')
return
} else {
setLoading(true)
}
try {
if(!!body) {
fetchInit.body = JSON.stringify(body)
}
const res = await fetch(baseURL+'/api/'+url, fetchInit)
if (res.statusText === 'OK') {
const resData = await res.json()
successCallback && successCallback(resData)
} else {
errorCallback && errorCallback()
throw new Error(res.statusText)
}
} catch (error) {
console.log(error.message)
} finally {
setLoading(false)
}
}
return {
loading,
setLoading,
fetchData
}
}
export default useFetch
```
使用
```js
import useFetch from '@/hooks/useFetch'
// 在函数组件中
//...
// 修改学生/添加学生
const { loading: updateLoading, fetchData: updateStu } = useFetch({
url: props.stu ? 'students/' + props.stu?.id : '/students',
type: props.stu ? 'PUT' : 'POST',
successCallback: ctx.fetchStuList,
})
const submitHandler = () => {
if (checkStu(stu) == false) {
console.log('检查输入')s
return
}
updateStu({ data: stu })
}
//...
```
## useMemo
```js
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```
把"创建"函数和依赖项数组作为参数传入 `useMemo`,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
传入 `useMemo` 的函数会在渲染期间执行。不要在这个函数内部执行不应该在渲染期间内执行的操作,诸如副作用这类的操作属于 `useEffect` 的适用范畴,而不是 `useMemo`。
`useMemo` 缓存的是函数的执行结果, `useCallbakc` 缓存的是函数, `React.memo` 是在子组件中使用
`useMemo` 也可以用来缓存 jsx/组件
如果没有提供依赖项数组,`useMemo` 在每次渲染时都会计算新的值。
```jsx
import { useState, useMemo } from 'react'
const sum = (a, b) => {
console.log('执行求和代码了')
const time = Date.now()
while (true) {
// 模拟逻辑特别复杂的代码
if (Date.now() - time > 2000) {
break
}
}
return a + b
}
const App = () => {
const [count, setCount] = useState(0)
// const num = sum(123,456)
// 缓存的是函数的执行结果
const num = useMemo(() => {
return sum(123, 456)
}, [])
return (
sum:{num}
count:{count}
)
}
export default App
```
## useImperativeHandle
```js
// 无法直接获取react组件的dom对象
// 例如 cont myref = useRef()
//
// 这样是获取不到的
// 用React.forwardRef()把组件包起来
// useImperativeHandle()自定义要返回的ref
// 把子组件的东西暴露给父组件,但不是直接暴露dom给父组件操作, 而是可以控制要暴露哪些值或方法
```
```js
useImperativeHandle(ref, createHandle, [deps])
```
```jsx
// 子组件
import { useState,useImperativeHandle } from 'react'
import React from 'react'
// 多了第二个参数ref
const Child = (props,ref) => {
const [inpuValue, setInputValue] = useState('')
const inputChangeHandler = (e)=>{
setInputValue(+e.target.value)
}
useImperativeHandle(ref,()=>{
// 决定要暴露哪些给父组件, 这里暴露了值和修改值的方法, 而不是直接暴露dom给父组件修改
return {
setInputValue,
value: inpuValue
}
})
return (
)
}
export default React.forwardRef(Child)
```
```jsx
import { useRef } from 'react'
import Child from './components/Child'
const App = () => {
const childRef = useRef()
return (
)
}
export default App
```
## useLayoutEffect
```mermaid
flowchart TB
%%classDef
classDef Purpule fill:#682b93,color:white;
classDef Blue fill:#3b67b9,color:white;
subgraph useInsertionEffect
direction TB
1[组件挂载]-->2[state改变]-->3[useInsertionEffect]-->4[DOM改变]-->5[绘制屏幕]
end
subgraph useLayoutEffect
direction TB
A[组件挂载]-->B[state改变]-->C[DOM改变]-->D[useLayoutEffect]-->E[绘制屏幕]
end
subgraph useEffect
direction TB
a[组件挂载]-->b[state改变]-->c[DOM改变]-->d[绘制屏幕]-->e[useEffect]
end
%%Style
class d,5,E Purpule;
class e,3,D Blue;
```
useLayoutEffect 的方法签名和 useEffect 一样,功能也类似。不同点在于,useLayoutEffect 的执行时机要早于 useEffect,它会在 DOM 改变后调用。在老版本的 React 中它和 useEffect 的区别比较好演示,React 18中,useEffect 的运行方式有所变化,所以二者区别不好演示。
uselayoutEffect 使用场景不多,实际开发中,在 effect 中需要修改元素样式,且使用 useEffect 会出现闪灯现象时可以使用 uselayoutEffect 进行昔换。
useInsertionEffect 场景: 用来设置动态样式, 在性能上减少重渲染和 dom 操作
```js
import { useRef,useLayoutEffect,useEffect,useInsertionEffect } from 'react'
const App = () => {
const myRef = useRef()
useEffect(() => {
console.log('useEffect', myRef) // 3.屏幕渲染之后,可能有白屏
})
useLayoutEffect(() => {
console.log('useLayoutEffect', myRef) // 2.屏幕渲染之前
})
useInsertionEffect(() => {
console.log('useInsertionEffect', myRef) // 1. undefined
})
return (
)
}
export default App
```
## useDebugValue
可以用来给钩子打标签, 开发者插件中可以看到
```js
// 自定义勾子
import { useEffect,useDebugValue } from 'react'
const useMyhook = () => {
useDebugValue('标签')
useEffect(()=>{
console.log('自定义钩子')
})
}
export default useMyhook
```

## useDeferredValue
设置一个延迟的 state
```js
/**
* 当多个组件使用同一个state时, 组件可能相互影响
* 一个组件卡顿,导致所有组件卡顿
* 此时设置延迟值,传给卡顿的组件,快的组件就不用等卡顿的组件了
* 实际体验: 快速改动value,触发deffredValue快速变化,
重渲染还是卡
*/
```
```jsx
// 模拟卡顿的组件
import React from 'react'
const items = []
for (let index = 0; index < 30; index++) {
items.push(`商品序号${index + 1}`)
}
const List = props => {
const filterList = (() => {
if (props.keyWord) {
return items.filter(item => {
return item.indexOf(props.keyWord) !== -1
})
} else {
return items
}
})()
// 模拟能卡的组件
const begin = Date.now()
while(true){
if(Date.now() - begin > 3000){
break
}
}
return (
{filterList.map(item => {
return - {item}
})}
)
}
export default React.memo(List)
```
```jsx
import { useDeferredValue } from 'react'
import { useState } from 'react'
import List from './components/List'
const App = () => {
console.log('组件重新渲染了')
const [value, setValue] = useState(0)
// useDeferredValue需要一个state作为参数
// 设置了延迟后, 每次修改state都会触发两次渲染
// 第一次'延迟值'是旧值, 第二次延迟值'是新值
// 延迟值总比原state慢
const deffredValue = useDeferredValue(value)
console.log('value', value)
console.log('deffredValue', deffredValue)
// 组件重新渲染了
// value 1
// deffredValue 0
// 组件重新渲染了
// value 1
// deffredValue 1
/**
* 当多个组件使用同一个state时, 组件可能相互影响
* 一个组件卡顿,导致所有组件卡顿
* 此时设置延迟值,传给卡顿的组件,快的组件就不用等卡顿的组件了
* 实际体验: 快速改动value,触发deffredValue快速变化,
重渲染还是卡
*/
return (
{
setValue(e.target.value)
}}
/>
{/* */}
)
}
export default App
```
## useTransition
让某个 setState 比其他的 setState 更慢执行
```js
// 模拟性能很差的组件
import React from 'react'
const items = []
for (let index = 0; index < 30; index++) {
items.push(`商品序号${index + 1}`)
}
const List = props => {
const filterList = (() => {
if (props.keyWord) {
return items.filter(item => {
return item.indexOf(props.keyWord) !== -1
})
} else {
return items
}
})()
// 模拟能卡的组件
const begin = Date.now()
while(true){
if(Date.now() - begin > 3000){
break
}
}
return (
{filterList.map(item => {
return - {item}
})}
)
}
export default React.memo(List)
```
```jsx
import { useTransition } from 'react'
import { useState } from 'react'
import List from './components/List'
const App = () => {
console.log('重新渲染')
const [value, setValue] = useState('')
const [keyWord, setKeyWord] = useState('')
// isPending可以获取startTransition的状态
const [isPending, startTransition] = useTransition()
const changeHandler = e => {
setValue(e.target.value)
// startTransition的回调函数中的setState会在其他的setState都生效后才执行
startTransition(() => {
setKeyWord(e.target.value)
})
}
return (
{!isPending &&
}
)
}
export default App
```
useDeferredValue 和 useTransition 都代替不了节流防抖