hooks
# Hooks
Hooks 是 React v16.8
新增功能
作用:为函数组件提供状态、生命周期等原本 class
组件中提供的 React
功能, Hooks
为函数组件钩入 class
组件的特征
R16.8
组件开发模式的对比
之前:
class
组件(提供状态) + 函数组件(展示内容)之后:
class
组件(提供状态) + 函数组件(展示内容)Hooks
(提供状态) + 函数组件(展示内容)
混用了以上两种方式
注意: 有了
hooks
,不能再把函数组件成为无状态组件,hooks
为函数提供了状态
# hooks优势
组件的逻辑逻辑复用,HOCs、render-props重构组件结构,导致组件形成了 JSX 嵌套地狱的问题
class 组件自身的问题
- class中的this指向问题
- 相互关联且需要对照修改的代码被拆分到不同生命周期函数中
- 不利于代码压缩和优化,也不利于 TS 的类型推导
- Hooks 只能在函数组件中使用,避免了 class 组件的问题
- 复用组件状态逻辑,而无需更改组件层次结构
- 根据功能而不是基于生命周期方法强制进行代码分割
- 抛开
React
赋予的概念来说,Hooks
就是一些普通的函数 - 具有更好的 TS 类型推导,tree-shaking 友好
# hooks基本使用
# useState ()
- 使用场景:当你想要在函数组件中,使用组件状态时,就要使用
useState
Hooks - 作用:为函数组件提供状态
使用示例
import {useState} from 'react'
const Count = ()=>{
const stateArray = useState(0)
const [state,setState] = stateArray[0]
return (
<div>
state
<button onClick={()=>setState(state+1)}>click</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
状态的读取和修改
读取状态:该方法提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用
修改状态: setCount
是一个函数,参数表示:新的状态值
组件更新的过程
函数组件使用 useState
后的执行过程,以及状态值的变化
组件第一次渲染
- 从头开始是执行该组件的代码逻辑
- 调用
useState(0)
将传入的参数作为状态初始值 - 渲染组件时,此时,获取到的状态值为
选进第二次渲染
- 点击按钮,调用
setCount(count+1)
修改状态,因为状态发生改变,所以,该组件会重新渲染 - 组件重新渲染时,会再次执行该组件中的代码逻辑
- 再次调用
useState(0)
, 此时React
内部会拿到最新值而非初始值 - 再次渲染组件,此时获取到的组件的状态值为最新值
# 使用规则
注意:React Hooks
只能直接出现在 函数组件 中,不能嵌套在 if/for
其它 函数中
React Hooks
必须每次组件渲染时,按照相同的顺序来调用所有的 Hooks
- 为什么会有这样的规则?因为
React
是按照Hooks
的调用顺序来识别每一个Hook
,如果每次调用的顺序不一样,导致React
无法知道哪一个是Hook
# useEffect ()
作用:处理函数组件中的副作用
对 React
组件来说,著作用就是根据数据流渲染 UI ,除此之外的都是副作用 . 如:数据请求 、动手修改 DOM 、localSorage 操作等
基本使用
import {useEffect} from 'react'
useEffect(()=>{
document.litle = `当前点击次数 ${count} 次`
}, , )
2
3
4
参数1:回调函数,就是在该函数中写会产生副作用的代码
执行实际:该 effect
会在组件渲染后以及组件更新后执行
相当于 : componentDidMount
+ componentDidUpdate
依赖项
描述:如果组件中有另一个状态,另一个状态状态更新,函数中的回调函数也会执行
性能优化:跳过不必要的执行,只在 count
变化时,才执行相应的 effect
useEffect(()=>{
document.litle = `当前点击次数 ${count} 次`
},[count])
2
3
参数2 : 可选的,可以传入一个数组,数组中的元素为副作用函数的依赖性 . 表示只有在依赖性改变时,才会重新执行 effect
空数组:该 effect
只会在组件第一次渲染后执行 ,相当于componentDidMount
// 触发时机:1 第一次渲染会执行 2 每次组件重新渲染都会再次执行
// componentDidMount + ComponentDidUpdate
useEffect(() => {})
// componentDidMount
// 触发时机:只在组件第一次渲染时执行
useEffect(() => {}, [])
// componentDidMount + componentDidUpdate(判断)
// 触发时机:1 第一次渲染会执行 2 当 count 变化时会再次执行
useEffect(() => {}, [count])
2
3
4
5
6
7
8
9
10
11
注意: 不要对 useEffect
的依赖项撒谎
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = '点击了' + count + '次'
}, [])
return (
<div>
<h1>计数器:{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<hr />
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
会在点击时没有效果产生
useEffect完全指南:https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/
# 清理副作用
在我们只想在 React
更新 DOM 之后运行一些额外的代码 ,比如 订阅外部数据源,开启定时器看,注册时间。。这种情况下,清除工作是非常重要的,可以防止内存泄露
useEffect(() => {
const handleResize = () => {}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
2
3
4
5
effect
回调函数的返回值,可选的,返回的是一个清理函数,用来执行事件解绑清理等操作
执行时机
- 组件卸载之前
- 重新执行
effect
回调函数之前
相当于 componentDidMount
+ componentWillUnmount
推荐:一个 useEffect
只处理一个功能,有多个功能时,使用多次 useEffect
优势:根据业务逻辑来拆分,相同功能的业务逻辑放在一起,而不是根据生命周期方法来拆分代码
# 发送网络请求
useEffect(() => {
// 是否取消本次请求
let didCancel = false
async function fetchMyAPI() {
let url = 'http://something/' + productId
let config = {}
const response = await myFetch(url)
// 如果开启其他请求,就忽略本次(过时)的请求结果
if (!didCancel) {
console.log(response)
}
}
fetchMyAPI()
return () => { didCancel = true } // 取消本次请求
}, [productId])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
effect
只能是一个同步函数,不能使用async
- 因为
effect
的返回值应该是一个清理函数,react
会在组件卸载或者effect
的依赖项变化时重新执行,但是如果effect
是async
的,此时返回值对象是promise
,无法保证清理函数被立即调用 - 为了使用
async/await
语法,可以在effect
内部创建async
函数并调用
注意
如果网络请求函数内访访问数据,可能会导致网络请求函数无线循环调用的问题,因为 effect
的执行是在数据发生更新时候去执行的
# 自定义Hooks
除了使用内置 Hooks
之外,还可以创建自己的 Hooks
作用:将组件状态逻辑提取到可重用的函数,实现状态逻辑复用
- 自定义
Hooks
是一个函数, 约定函数名称必须以useX
开头,React
通过函数名词是否以use
开头来判断是不是hooks
Hooks
只能在函数组件中或其它自定义Hooks
中使用- 自定义
Hooks
用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值
将事件处理程序放在 useEffect 内部
// 1 将 resize 事件处理程序放在 effect 回调中,当前这个代码是没有问题的
useEffect(() => {
const handleResize = () => {
console.log('window 窗口大小改变了')
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
// 2 将 resize 事件处理程序拿到 useEffect 的外部,当前这个代码是没有问题的
const handleResize = () => {
console.log('window 窗口大小改变了')
}
useEffect(() => {
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
// 3 有依赖项的情况:
useEffect(() => {
// resize 事件的处理程序
const handleResize = () => {
console.log('window 窗口大小改变了', count)
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [count])
// 注意:此处的代码,会给一些警告!!! 不要按照这种方式写代码!!!
// 4 如果将 handleResize 放到了 useEffect 外部,React 会给以警告:
// 要么将 handleResize 放到 useEffect 中
// 要么使用 useCallback 这个 hook 来包裹 handleResize
// resize 事件的处理程序
const handleResize = () => {
console.log('window 窗口大小改变了', count)
}
useEffect(() => {
console.log('useeffect 执行了')
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [handleResize])
// 总结以上几种情况,推荐:在给 window 绑定事件时,将 事件处理程序放在 useEffect 内部。
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58