React Hooks基础
- React Hooks 介绍
- React Hooks 基础
React Hooks 介绍
- Hooks 是什么
- 为什么要有 Hooks
Hooks 是什么
Hooks
:钩子、钓钩、钩住Hooks
是 React v16.8 中的新增功能- 作用:为函数组件提供状态、生命周期等原本 class 组件中提供的 React 功能
- 可以理解为通过 Hooks 为函数组件钩入 class 组件的特性
- 注意:Hooks 只能在函数组件中使用,自此,函数组件成为 React 的新宠儿
React v16.8 版本前后,组件开发模式的对比:
- React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)
- React v16.8 及其以后:
- class 组件(提供状态) + 函数组件(展示内容)
- Hooks(提供状态) + 函数组件(展示内容)
- 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件
注意1:虽然有了 Hooks,但 React 官方并没有计划从 React 库中移除 class。
注意2:有了 Hooks 以后,不能再把函数组件称为无状态组件了,因为 Hooks 为函数组件提供了状态。
为什么要有 Hooks
两个角度:1 组件的状态逻辑复用 2 class 组件自身的问题
-
组件的状态逻辑复用:
- 在 Hooks 之前,组件的状态逻辑复用经历了:mixins(混入)、HOCs(高阶组件)、render-props 等模式。
- (早已废弃)mixins 的问题:1 数据来源不清晰 2 命名冲突。
- HOCs、render-props 的问题:重构组件结构,导致组件形成 JSX 嵌套地狱问题。
-
class 组件自身的问题:
- 选择:函数组件和 class 组件之间的区别以及使用哪种组件更合适
- 需要理解 class 中的 this 是如何工作的
- 相互关联且需要对照修改的代码被拆分到不同生命周期函数中
- componentDidMount -> window.addEventListener(‘resize’, this.fn)
- componentWillUnmount -> window.removeEventListener(‘resize’, this.fn)
- 相比于函数组件来说,不利于代码压缩和优化,也不利于 TS 的类型推导
正是由于 React 原来存在的这些问题,才有了 Hooks 来解决这些问题
hooks的优势
由于原来 React 中存在的问题,促使 React 需要一个更好的自带机制来实现组件状态逻辑复用。
- Hooks 只能在函数组件中使用,避免了 class 组件的问题
- 复用组件状态逻辑,而无需更改组件层次结构
- 根据功能而不是基于生命周期方法强制进行代码分割
- 抛开 React 赋予的概念来说,Hooks 就是一些普通的函数
- 具有更好的 TS 类型推导
- tree- - shaking 友 好,打包时去掉未引用的代码
- 更好的压 缩
项目开发中,Hooks 的采用策略:
- 不推荐直接使用 Hooks 大规模重构现有组件
- 推荐:新功能用 Hooks,复杂功能实现不了的,也可以继续用 class
- 找一个功能简单、非核心功能的组件开始使用 hooks
前面学习的 React 知识是有用的
class 组件相关的 API 不用了,比如:
class Hello extends Component
componentDidMount
、componentDidUpdate
、componentWillUnmount
this
相关的用法
原来学习的内容还是要用的,比如:
- JSX:
{}
、onClick={handleClick}
、条件渲染、列表渲染、样式处理等 - 组件:函数组件、组件通讯
- 路由
- React 开发理念:
单向数据流
、状态提升
等 - 解决问题的思路、技巧、常见错误的分析等上
useState Hook
概述
问题:Hook 是什么? 一个 Hook 就是一个特殊的函数,让你在函数组件中获取状态等 React 特性
使用模式:函数组件 + Hooks
特点:从名称上看,Hook 都以 use 开头
useState Hook 的基本使用
- 使用场景:当你想要在函数组件中,使用组件状态时,就要使用 useState Hook 了
- 作用:为函数组件提供状态(state)
- 使用步骤:
- 导入
useState
函数 - 调用
useState
函数,并传入状态的初始值 - 从
useState
函数的返回值中,拿到状态和修改状态的函数 - 在 JSX 中展示状态
- 在按钮的点击事件中调用修改状态的函数,来更新状态
- 导入
import { useState } from 'react'
const Count = () => {
// 返回值是一个数组
const stateArray = useState(0)
// 状态值 -> 0
const state = stateArray[0]
// 修改状态的函数
const setState = stateArray[1]
return (
<div>
{/* 展示状态值 */}
<h1>useState Hook -> {state}</h1>
{/* 点击按钮,让状态值 +1 */}
<button onClick={() => setState(state + 1)}>+1</button>
</div>
)
}
- 参数:状态初始值。比如,传入 0 表示该状态的初始值为 0
- 注意:此处的状态可以是任意值(比如,数值、字符串等),而 class 组件中的 state 必须是对象
- 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
使用数组解构简化
比如,要获取数组中的元素:
- 原始方式:索引访问
const arr = ['aaa', 'bbb']
const a = arr[0] // 获取索引为 0 的元素
const b = arr[1] // 获取索引为 1 的元素
- 简化方式:数组解构
- 相当于创建了两个变量(可以是任意的变量名称)分别获取到对应索引的数组元素
const arr = ['aaa', 'bbb']
const [a, b] = arr
// a => arr[0]
// b => arr[1]
const [state, setState] = arr
- 使用数组解构简化
useState
的使用- 约定:修改状态的函数名称以 set 开头,后面跟上状态的名称
// 解构出来的名称可以是任意名称
const [state, setState] = useState(0)
const [age, setAge] = useState(0)
const [count, setCount] = useState(0)
状态的读取和修改
状态的使用:1 读取状态 2 修改状态
-
读取状态:该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用
-
修改状态:
setCount(newValue)
是一个函数,参数表示:新的状态值- 调用该函数后,将使用新的状态值
替换
旧值 - 修改状态后,因为状态发生了改变,所以,该组件会重新渲染
组件的更新过程
函数组件使用 useState hook 后的执行过程,以及状态值的变化:
-
组件第一次渲染:
- 从头开始执行该组件中的代码逻辑
- 调用
useState(0)
将传入的参数作为状态初始值,即:0 - 渲染组件,此时,获取到的状态 count 值为: 0
-
组件第二次渲染:
- 点击按钮,调用
setCount(count + 1)
修改状态,因为状态发生改变,所以,该组件会重新渲染 - 组件重新渲染时,会再次执行该组件中的代码逻辑
- 再次调用
useState(0)
,此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1 - 再次渲染组件,此时,获取到的状态 count 值为:1
- 点击按钮,调用
注意:useState 的初始值(参数)只会在组件第一次渲染时生效。
也就是说,以后的每次渲染,useState 获取到都是最新的状态值。React 组件会记住每次最新的状态值!
为函数组件添加多个状态
问题:如果一个函数组件需要多个状态,该如何处理?
回答:调用 useState
Hook 多次即可,每调用一次 useState Hook 可以提供一个状态。
注意:useState Hook 多次调用返回的 [state, setState] 相互之间,互不影响。
hooks 的使用规则【重要】
注意:React Hooks 只能直接出现在 函数组件 中,不能嵌套在 if/for/其他函数中!
否则就会报错:React Hook “useState” is called conditionally. React Hooks must be called in the exact same order in every component render
React 的 useState 这个 Hook 被条件性(放在一个条件判断中)的调用了。
React Hooks 必须要每次组件渲染时,按照相同的顺序来调用所有的 Hooks。
- 为什么会有这样的规则? 因为 React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook
- 通过开发者工具可以查看到。
useEffect Hook
- side effect - 副作用
- useEffect 的基本使用
- useEffect 的依赖
- useEffect 发送请求
side effect - 副作用
使用场景:当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了
作用:处理函数组件中的副作用(side effect)
问题:副作用(side effect)是什么?
回答:在计算机科学中,如果一个函数或其他操作修改了其局部环境之外的状态变量值,那么它就被称为有副作用
类比,对于 999 感冒灵感冒药来说:
- (主)作用:用于感冒引起的头痛,发热,鼻塞,流涕,咽痛等
- 副作用:可见困倦、嗜睡、口渴、虚弱感
理解:副作用是相对于主作用来说的,一个功能(比如,函数)除了主作用,其他的作用就是副作用
对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM,发送请求,绑定事件)
React 组件的公式:UI = f(state)
常见的副作用(side effect)
- 数据(Ajax)请求、手动修改 DOM、localStorage 操作等
useEffect 的基本使用
使用场景:当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了
作用:处理函数组件中的副作用(side effect)
注意:在实际开发中,副作用是不可避免的。因此,react 专门提供了 useEffect Hook 来处理函数组件中的副作用
import { useEffect } from 'react'
useEffect(function effect() {
document.title = `当前已点击 ${count} 次`
})
useEffect(() => {
document.title = `当前已点击 ${count} 次`
})
解释:
- 参数:回调函数(称为 effect),就是在该函数中写副作用代码
- 执行时机:该 effect 会在组件渲染后以及组件更新后执行
- 相当于componentDidMount + componentDidUpdate
useEffect 的依赖
- 问题:如果组件中有另外一个状态,另一个状态更新时,刚刚的 effect 回调,也会执行
- 性能优化:跳过不必要的执行,只在 count 变化时,才执行相应的 effect
useEffect(() => {
document.title = `当前已点击 ${count} 次`
}, [count])
解释:
- 第二个参数:可选的,可省略;也可以传一个数组,数组中的元素可以成为依赖项(deps)
- 该示例中表示:只有当 count 改变时,才会重新执行该 effect
useEffect 的依赖是一个空数组
useEffect 的第二个参数,还可以是一个空数组([]),表示只在组件第一次渲染后执行 effect
使用场景:1 事件绑定 2 发送请求获取数据 等
useEffect(() => {
const handleResize = () => {}
window.addEventListener('resize', handleResize)
}, [])
解释:
- 该 effect 只会在组件第一次渲染后执行,因此,可以执行像事件绑定等只需要执行一次的操作
- 此时,相当于 class 组件的 componentDidMount 钩子函数的作用
- 跟 useState Hook 一样,一个组件中也可以调用 useEffect Hook 多次
- 推荐:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect
总结 useEffect 的使用
// 触发时机:1 第一次渲染会执行 2 每次组件重新渲染都会再次执行
// componentDidMount + ComponentDidUpdate
useEffect(() => {})
// componentDidMount
// 触发时机:只在组件第一次渲染时执行
useEffect(() => {}, [])
// componentDidMount + componentDidUpdate(判断)
// 触发时机:1 第一次渲染会执行 2 当 count 变化时会再次执行
useEffect(() => {}, [count])
不要对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>
)
}
useEffect完全指南:https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/
useEffect 清理副作用
有时候,我们只想**在 React 更新 DOM 之后运行一些额外的代码。**比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。
还有一些副作用是需要清除的。例如订阅外部数据源, 开启定时器,注册事件。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!
问题:如何在组件卸载时,解绑事件?此时,就用到 effect 的返回值了
useEffect(() => {
const handleResize = () => {}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
解释:
- effect 的返回值也是可选的,可省略。也可以返回一个清理函数,用来执行事件解绑等清理操作
- 清理函数的执行时机:1 组件卸载时 2 effect 重新执行前
- 此时,相当于 class 组件的 componentWillUnmount 钩子函数的作用
- 推荐:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect
- 优势:根据业务逻辑来拆分,相同功能的业务逻辑放在一起,而不是根据生命周期方法名称来拆分代码
- 编写代码时,关注点集中;而不是上下翻滚来查看代码
将事件处理程序放在 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 内部。
useEffect 发送请求
在组件中,使用 useEffect Hook 发送请求获取数据(side effect):
useEffect(() => {
const loadData = async () => {}
loadData()
}, [])
解释:
- 注意:effect 只能是一个同步函数,不能使用 async
- 因为 effect 的返回值应该是一个清理函数,React 会在组件卸载或者 effect 的依赖项变化时重新执行
- 但如果 effect 是 async 的,此时返回值是 Promise 对象。这样的话,就无法保证清理函数被立即调用
- 如果延迟调用清理函数,也就没有机会忽略过时的请求结果或取消请求
- 为了使用 async/await 语法,可以在 effect 内部创建 async 函数,并调用
// 错误演示:
// 不要给 effect 添加 async
useEffect(async () => {}, [])
// https://github.com/facebook/react/issues/14326#issuecomment-441680293
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])
useEffect循环报错问题
useEffect(() => {
const getList = async () => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
setList(res.data.data.channels)
}
console.log(list)
getList()
}, [list])
自定义hooks
除了使用内置的 Hooks 之外,还可以创建自己的 Hooks(自定义 Hooks)。
使用场景:将组件状态逻辑提取到可重用的函数(自定义 Hooks)中,实现状态逻辑复用。
内置 Hooks 为函数组件赋予了 class 组件的功能;在此之上,自定义 Hooks 针对不同组件实现不同状态逻辑复用。
-
自定义 Hooks 是一个函数,约定函数名称必须以 use 开头,React 就是通过函数名称是否以 use 开头来判断是不是 Hooks
-
Hooks 只能在函数组件中或其他自定义 Hooks 中使用,否则,会报错!
-
自定义 Hooks 用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值(就像使用普通函数一样)
Hooks其他API
useRef hook
1.useRef 能够创建一个ref对象,有current属性,{current:null}
const xxRef=useRef(null)
2.通过ref属性关联到某个DOM上,{current:DOM}
3.通过xxRef.current访问到对应的DOM
useContext hook
跨组件通信
import React, { useContext, useState } from 'react'
// 1 创建Context对象
const Context = React.createContext()
export default function App() {
const [color, setColor] = useState('red')
return (
// 根节点 通过value提供数据
<Context.Provider value={color}>
<div>
<h1>我是根组件</h1>
<div>颜色:{color}</div>
<button onClick={() => setColor('yellow')}>修改</button>
<Father></Father>
</div>
</Context.Provider>
)
}
const Father = () => {
return (
<div>
<h3>我是父组件</h3>
<Child></Child>
</div>
)
}
const Child = () => {
// useContext 获取数据
const color = useContext(Context)
return (
<div>
<h5>我是子组件---{color}</h5>
</div>
)
}
Hooks进阶
概述
根据前面的学习我们知道,Hooks(内置或自定义)只能在函数组件中使用。因此,Hooks 与函数组件是密不可分的。
所以,要想深入理解 Hooks,就必须先理解函数组件的特性,因为这些特性会影响到了 Hooks 的使用。
本节,我们先来理解函数组件的特性。然后,根据这些特性对实际开发产生的影响,我们再来学习 useCallback / useMemo / useRef 等内置 Hooks。
最后,我们来模拟实现 useState / useEffect 这两个 Hooks,来深入理解 Hooks 的实现原理。
函数组件的特性
React 中的函数组件是通过函数来实现的,函数组件的公式:f(state) => UI
,即:数据到视图的映射。
函数组件本身很简单,但因为是通过函数实现的,所以,在使用函数组件时,就会体现出函数所具有的特性来。
函数组件的特性说明:
- 对于函数组件来说,每次状态更新后,组件都会重新渲染。
- 并且,每次组件更新都像是在给组件拍照。每张照片就代表组件在某个特定时刻的状态。
- 或者说:
组件的每次特定渲染,都有自己的 props/state/事件处理程序
等。 - 这些照片记录的状态,从代码层面来说,是通过 JS 中函数的闭包机制来实现的。
这就是 React 中函数组件的特性,更加的函数式(利用函数的特性)
import { useState } from 'react'
import ReactDOM from 'react-dom'
// 没有 hooks 的函数组件:
const Counter = ({ count }) => {
// console.log(count)
const showCount = () => {
setTimeout(() => {
console.log('展示 count 值:', count)
}, 3000)
}
return (
<div>
<button onClick={showCount}>点击按钮3秒后显示count</button>
</div>
)
}
const App = () => {
const [count, setCount] = useState(0)
return (
<div>
<h1>计数器:{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<hr />
{/* 子组件 */}
<Counter count={count} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
函数组件特性带来的问题
函数组件的特性:组件的每次特定渲染,都有自己的 props/state/事件处理程序 等。
该特性,导致了几个问题:
- 组件每次重新渲染时,组件内部的事件处理程序等函数都会重新创建,导致子组件每次都会接收到不同的 props,从而重复进行不必要的渲染(性能问题)。
- 组件内的事件处理程序等函数中,只能获取到那一次特定渲染时的数据,这是合理的(闭包的原因)。
说明:函数组件配合 Hooks 使用时,会不会因为闭包以及每次都创建新的函数等,让组件变慢?答案:不会!
注意:在没有发现性能问题前,避免过早的性能优化。如果要优化,一定要考虑优化成本是否大于优化后的价值。
对于第一个问题,我们使用 React.memo
配合 useCallback/useMemo
这两个 Hooks 来解决。
对于第二个问题,我们使用 useRef Hook
来解决。
React.memo高阶组件
介绍
React.memo 高阶组件的使用场景说明:
React 组件更新机制:只要父组件状态更新,子组件就会无条件的一起更新。
- 子组件 props 变化时更新过程:组件代码执行 -> JSX Diff(配合虚拟 DOM)-> 渲染(变化后的内容)【 DOM 操作】。
- 子组件 props 无变化更新过程:组件代码执行 -> JSX Diff(配合虚拟 DOM)【无 DOM 操作】。
注意:此处更新指的是组件代码执行、JSX 进行 Diff 操作(纯 JS 的操作,速度非常快,不会对性能产生太多影响)。
- 如果组件 props 改变了,那么,该组件就必须要更新,才能接收到最新的 props。
- 但是,如果组件 props 没有改变时,组件也要进行一次更新。实际上,这一次更新是没有必要的。
如果要避免组件 props 没有变化而进行的不必要更新(Diff),这种情况下,就要使用 React.memo 高阶组件。
注:对于 class 组件来说,可以使用 PureComponent 或 shouldComponentUpdate 钩子函数来实现
。
import { useState } from 'react'
import ReactDOM from 'react-dom'
const Child2 = ({ count }) => {
console.log('Child2 子组件代码执行了')
return <div style={{ backgroundColor: '#abc' }}>子组件2:{count}</div>
}
const Child1 = () => {
console.log('Child1 子组件代码执行了')
return <div style={{ backgroundColor: '#def' }}>子组件1</div>
}
const App = () => {
const [count, setCount] = useState(0)
return (
<div style={{ backgroundColor: 'pink', padding: 10 }}>
<h1>计数器:{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<hr />
{/* 子组件 */}
<Child1 />
<br />
<Child2 count={count} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
语法
使用场景:当你想要避免函数组件 props 没有变化而产生的不必要更新时,就要用到 React.memo 了。
作用:记忆组件上一次的渲染结果,在 props 没有变化时复用该结果,避免函数组件不必要的更新。
解释:
- React.memo 是一个高阶组件,用来记忆(memorize)组件。
- 参数(Child):需要被记忆的组件,或者说是需要避免不必要更新的组件。
- 返回值(MemoChild):React 记住的 Child 组件。
原理:通过对比检查更新前后 props 是否相同,来决定是否复用上一次的渲染结果,
- 如果相同,复用上一次的渲染结果;
- 如果不同,重新渲染组件。
并不是所有的组件都适合使用memo,比如child2组件,每次都需要重新渲染,使用memo反而会使性能变得更低,逻辑也变得更复杂
浅层对比
默认情况下,React.memo 只会对更新前后的 props 进行浅对比(shallow compare)与 PureComponent 相同。
也就是说,对于对象类型的 prop 来说,只会比较引用
- 如果更新前后的引用相同,复用上一次的渲染结果(不会重新渲染该组件)。
- 如果更新前后的引用不同,重新渲染该组件。
如果你要手动控制比较过程,可以使用 React.memo 的第二个参数:
解释:
- 第二个参数:用来比较更新前后 props 的函数。
- 返回值:如果返回 true,表示记住(不重新渲染)该组件;如果返回 false,表示重新渲染该组件。
useCallback高阶组件
使用场景
在使用 React.memo 时,对于对象类型的 props,只会比较引用(浅对比)。
但是,因为组件每次更新都会创建新的 props 值,比如,新的对象、事件处理程序等(函数组件的特性)。
这就导致:React.memo 在处理对象类型的 props 时,会失效(每次的 props 都是新对象)。
但是,我们还是想让 React.memo 在处理对象类型的 props 时,也有效。
为了让 React.memo 处理对象类型的 props 有效,只要在组件更新期间保持对象类型引用相等,就可以了。
这时候,就要用到以下两个 Hooks:
useCallback
Hook:记住函数的引用,在组件每次更新时返回相同引用的函数。useMemo
Hook:记住任意数据(数值、对象、函数等),在组件每次更新时返回相同引用的数据【功能之一】
基本使用
使用场景:在使用 React.memo 时,为了组件每次更新时都能获取到相同引用的函数,就要用到 useCallback Hook
注意:需要配合 React.memo 高阶函数一起使用。
作用:记忆传入的回调函数,这个被记住的回调函数会一直生效,直到依赖项发生改变
解释:
- 第一个参数:必选,需要被记忆的回调函数。
- 第二个参数:必选,依赖项数组,用于指定回调函数中依赖(用到)的数据(类似于 useEffect 的第二个参数)。
- 即使没有依赖,也得传入空数组([]),此时,useCallback 记住的回调函数就会一直生效。
- 返回值:useCallback 记住的回调函数。
- useCallback 记住的回调函数会一直生效(或者说会一直返回同一个回调函数),直到依赖项发生改变。