Hook 是 React 的特性,作用是可以在不编写 Class 的情况下(函数组件)使用 state 以及其他 React 特性。
Hook 使你在无需修改组件结构的情况下复用状态逻辑。
复杂组件的生命周期中可能包含很多逻辑,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
使用class 组件要理解 JavaScript 中 this
的工作方式,Hook 使你在非 class 的情况下可以使用更多的 React 特性。
React 是如何把对 Hook 的调用和组件联系起来的?
React 保持对当前渲染中的组件的追踪。
每个组件内部都有一个「记忆单元格」列表。它们只不过是用来存储数据的 JavaScript 对象。当你用 useState()
调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化),然后把指针移动到下一个。这就是多个 useState()
调用会得到各自独立的本地 state 的原因。
useState
做了什么:方括号定义一个 “state 变量”,数组解构-useState
返回的数组中的两个值
当我们更新一个 state 变量,setState 会 替换 它的值。这和 class 中的 this.setState
不一样,后者会把更新后的字段 合并 入对象中。
(如果在一次 useState()
调用中传入一个包含多个 state 的对象。但是只更新其中一部分state 的值,setState 时也需要展开 「...state」 以确保数据的完整,因为setState 是对state 进行替换的操作。 而this.setState
中只需要写更新的那些数据,它会合并到对象中。)
useEffect
componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个生命周期函数的组合。
如果在组件加载和更新时,即在每次渲染之后执行同样的操作,就要在 componentDidMount
(组件加载)和 componentDidUpdate
(组件更新)函数中写重复的代码。
与 componentDidMount
或 componentDidUpdate
不同,使用 useEffect
调度的 effect 不会阻塞浏览器更新屏幕,大多数情况下 effect 不需要同步地执行。
两种常见副作用操作:需要清除的和不需要清除的。
不需要清除的:在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志。在执行完这些操作之后,就可以忽略他们了。
需要清除的:如订阅外部数据源,这种操作的清除工作非常重要,防止引起内存泄露。在 componentDidMount
中设置订阅,并在 componentWillUnmount
中清除。
useEffect
做了什么? 告诉 React 组件在渲染后执行某些操作。React 会保存其中传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。、
在 effect 中返回return一个清除函数, 这是 effect 可选的清除机制,React 会在组件卸载的时候执行清除操作。如此可以将添加和移除订阅的逻辑放在一起。
优点:可以使用多个 effect,将不相关逻辑按照代码的用途分离到不同的 effect 中。
useEffect
默认会在调用一个新的 effect 之前对前一个 effect 进行清理,避免了在 class 组件中因为没有处理更新逻辑而导致常见的 bug。
将 useEffect
放在组件内部让我们可以在 effect 中直接访问 count
state 变量(或其他 props)。(Hook 使用了 JavaScript 的闭包机制)
通过跳过 Effect 进行性能优化
仅在指定变量更改时,调用effect:将指定变量以数组形式 作为 useEffect
的第二个可选参数。组件重渲染时,如果数组中的所有元素都与原来相等,React 就会跳过这个 effect,从而实现了性能的优化。即使数组只有一个元素发生变化,React 也会执行 effect。
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)作为第二个参数。
effect 的执行时机
与 componentDidMount
、componentDidUpdate
不同的是,传给 useEffect
的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因为绝大多数操作不应阻塞浏览器对屏幕的更新。
然而,并非所有 effect 都可以被延迟执行。例如,一个对用户可见的 DOM 变更就必须在浏览器执行下一次绘制前被同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React 为此提供了一个额外的 useLayoutEffect
Hook 来处理这类 effect。它和 useEffect
的结构相同,区别只是调用时机不同。
虽然 useEffect
会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。在开始新的更新前,React 总会先清除上一轮渲染的 effect。
useContext
复用一段上下文、文本
useReducer
接收参数:一个形如 (state, action) => newState
的 reducer、和state的初始值
;
返回数组:当前的 state 以及与其配套的 dispatch
方法
useCallback
接收参数:内联回调函数、依赖项数组(回调函数依赖的变量组成的数组);
返回:该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
。
useRef
返回一个可变的 ref 对象
将 useRef 对象以 <div ref={myRef} />
形式传入组件,React 都会将 useRef 对象的 .current
属性设置为相应的 DOM 节点(当前组件)。