啥是 React Hooks
在 React 里,以前我们写组件主要用类(class)的方式,写起来有点复杂,尤其是处理状态和副作用的时候。React Hooks 就是 React 16.8 之后推出的新特性,它能让我们不用写类,直接在函数组件里使用状态和其他 React 特性,让代码更简洁、更易复用。
useState 的使用方法与原理
使用方法
useState
就像是给函数组件装了个“小抽屉”,可以用来存放和管理状态。下面是一个简单的计数器示例:
import React, { useState } from'react';
function Counter() {
// 定义一个名为 count 的状态,初始值为 0
// useState 返回一个数组,第一个元素是状态的值,第二个元素是更新这个状态的函数
const [count, setCount] = useState(0);
return (
<div>
{/* 显示 count 的值 */}
<p>你点击了 {count} 次</p>
{/* 点击按钮时调用 setCount 函数,将 count 的值加 1 */}
<button onClick={() => setCount(count + 1)}>
点击我
</button>
</div>
);
}
export default Counter;
在这个例子里,我们用 useState
创建了一个状态 count
,初始值是 0。每次点击按钮,就调用 setCount
函数来更新 count
的值。
原理
React 内部维护了一个状态链表。当我们调用 useState
时,React 会根据调用的顺序,从这个链表中取出对应位置的状态值。每次组件重新渲染,useState
都会按照相同的顺序从链表中获取状态。所以,useState
必须在组件的顶层调用,不能在条件判断、循环或者嵌套函数里调用,不然会打乱这个顺序,导致状态获取出错。
useEffect 的使用方法与原理
使用方法
useEffect
就像是组件的“小助手”,可以在组件渲染后执行一些额外的操作,比如数据获取、订阅事件、修改 DOM 等,这些操作就叫做副作用。下面是一个简单的数据获取示例:
import React, { useState, useEffect } from'react';
function DataFetcher() {
// 定义一个名为 data 的状态,初始值为 null
const [data, setData] = useState(null);
// 使用 useEffect 进行数据获取
useEffect(() => {
// 模拟一个异步的数据获取操作
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const result = await response.json();
// 将获取到的数据更新到 data 状态中
setData(result);
};
// 调用数据获取函数
fetchData();
// 可以返回一个清理函数,用于在组件卸载时执行一些清理操作,比如取消订阅等
return () => {
console.log('组件卸载了');
};
}, []); // 传入空数组,表示这个 useEffect 只在组件第一次渲染后执行
return (
<div>
{data? (
// 如果 data 不为 null,显示数据的标题
<p>{data.title}</p>
) : (
// 如果 data 为 null,显示加载中
<p>加载中...</p>
)}
</div>
);
}
export default DataFetcher;
在这个例子里,我们用 useEffect
模拟了一个数据获取的操作。useEffect
接收两个参数,第一个是一个回调函数,里面放着要执行的副作用操作;第二个是一个可选的数组,用于指定哪些值发生变化时才执行这个副作用。如果不传这个数组,副作用会在每次组件渲染后都执行;如果传一个空数组,副作用只会在组件第一次渲染后执行。
原理
React 会在组件渲染后记录下所有的 useEffect
回调函数。当组件重新渲染时,React 会先执行上一次 useEffect
返回的清理函数(如果有的话),然后再执行新的 useEffect
回调函数。通过传入的依赖数组,React 可以判断哪些 useEffect
需要重新执行。如果依赖数组里的值没有变化,对应的 useEffect
就不会重新执行,这样可以避免不必要的副作用操作,提高性能。
总结
useState
让函数组件可以有自己的状态,就像给组件装了个“小抽屉”来存放数据;useEffect
让函数组件可以在渲染后执行副作用操作,就像给组件请了个“小助手”来处理额外的事情。它们的原理都是基于 React 内部的机制,通过维护状态链表和记录副作用回调函数来实现相应的功能。
除了useState,React Hooks还有哪些常用的Hook?
除了 useState
外,React Hooks还有很多常用的Hook,以下是一些常见的Hook及其用法:
useEffect
- 作用:相当于React类组件中的
componentDidMount
、componentDidUpdate
和componentWillUnmount
这几个生命周期函数的组合。可以用来处理一些副作用操作,像发送网络请求获取数据、添加订阅、操作DOM以及设置定时器等。 - 举例:比如在一个新闻列表组件里,你可以用
useEffect
在组件加载后发送请求获取新闻数据,并且在组件卸载时清理相关资源,防止内存泄漏。
useContext
- 作用:能让组件方便地获取到上下文(Context)中的数据,不用再通过一层一层地传递props这种麻烦的方式。适用于需要在多个组件之间共享数据,比如用户登录状态、主题设置等。
- 举例:假如你的应用有一个全局的用户登录状态,很多组件都需要用到这个状态来判断是否显示某些内容,就可以用
useContext
让各个组件轻松获取到这个登录状态,而不用在每个组件的props里传来传去。
useReducer
- 作用:和
useState
类似,也是用来管理状态的。不过它更适合用于管理复杂的状态逻辑,比如状态的更新依赖于之前的状态,或者有多个不同类型的状态更新操作。 - 举例:在一个购物车应用里,管理购物车中商品的添加、删除、数量修改等复杂操作就可以用
useReducer
,把这些操作的逻辑集中在一个地方处理,让代码更清晰。
useMemo
- 作用:可以用来缓存一个值,这个值是通过某个函数计算出来的。只有当它的依赖项发生变化时,才会重新计算这个值,否则就直接使用缓存的值,这样能提高性能,避免一些不必要的计算。
- 举例:在一个计算商品总价的组件里,如果商品列表很长,计算总价是个比较耗时的操作。用
useMemo
就可以在商品列表或价格等相关依赖项变化时才重新计算总价,其他时候直接用缓存的结果,加快组件的渲染速度。
useCallback
- 作用:主要用于缓存函数,返回一个 memoized 回调函数。它可以确保在组件重新渲染时,如果依赖项没有变化,函数的引用就不会改变,这在将函数作为props传递给子组件,并且子组件依赖于函数的引用不变来进行优化时非常有用。
- 举例:比如有一个父组件要把一个点击事件处理函数传递给子组件,用
useCallback
可以保证只要点击事件处理函数里用到的变量没有变化,这个函数的引用就不会变,这样子组件就不会因为父组件重新渲染而不必要地重新渲染。
useRef
- 作用:可以用来创建一个对DOM元素或者组件实例的引用,也可以用来在组件的多次渲染之间保存一些可变的值,而且不会导致组件重新渲染。
- 举例:在一个输入框组件里,你可以用
useRef
获取到输入框的DOM元素,方便进行聚焦、获取输入值等操作。还可以用它来保存一些临时的数据,比如保存上一次的某个状态值,在组件渲染时进行对比等。
除了上述Hook,React还有哪些不常用但很有用的Hook?
除了上述Hook外,React还有一些不常用但很有用的Hook,以下是几个典型的例子:
useLayoutEffect
- 作用:和
useEffect
非常相似,也是用于处理副作用。但它的执行时机更特殊,是在所有的DOM变更都已经同步完成后,但在浏览器进行绘制之前执行。这使得它适合用于需要读取或操作DOM布局的场景,比如根据DOM元素的尺寸或位置来进行一些计算或调整。 - 使用场景:当你需要根据组件的初始渲染或更新后的DOM布局来进行一些操作时,
useLayoutEffect
就很有用。比如实现一个根据页面滚动位置来动态调整样式的粘性导航栏,需要在每次页面布局更新后立即获取滚动位置并进行样式调整,就可以用useLayoutEffect
。
useImperativeHandle
- 作用:通常用于在父组件中访问子组件的实例方法或属性。它可以让你自定义通过
ref
传递给父组件的内容,而不是直接暴露子组件的所有实例属性和方法,从而实现更精细的控制和封装。 - 使用场景:在一些复杂的表单场景中,父组件可能需要调用子组件的特定方法来进行表单验证或获取表单数据。通过
useImperativeHandle
,子组件可以将一些公共的、供父组件调用的方法暴露出来,而隐藏内部的实现细节。
useMutationEffect
- 作用:这是一个实验性的Hook,用于在DOM发生变化时执行副作用。它的主要特点是可以更精细地控制副作用的执行时机,特别是在处理DOM突变相关的操作时更灵活。
- 使用场景:当你需要对DOM的插入、删除或属性变化等操作进行更底层的监听和处理时,
useMutationEffect
可能会派上用场。比如实现一个实时的DOM变化跟踪工具,用于记录页面上元素的添加、删除或属性修改等操作。
useDebugValue
- 作用:主要用于在React开发者工具中显示自定义的调试信息,帮助开发者更好地理解组件的状态和行为。它可以为自定义的Hook添加一个标签,方便在调试时快速识别和查看Hook的相关数据。
- 使用场景:在开发复杂的自定义Hook时,使用
useDebugValue
可以让你在开发者工具中为这个Hook显示更有意义的调试信息。比如你创建了一个用于处理用户权限的自定义Hook,通过useDebugValue
可以显示当前用户的权限级别等信息,方便在调试时快速定位问题。
useDeferredValue
- 作用:允许将某些状态更新延迟到更合适的时机进行处理,以提高应用的响应性。它可以将一个值标记为“可延迟”,这样当这个值发生变化时,不会立即触发组件的重新渲染,而是等到浏览器有空闲时间时再进行处理。
- 使用场景:在处理一些非关键的、但可能会导致大量计算或渲染的状态更新时,
useDeferredValue
很有帮助。比如在一个搜索结果列表中,用户输入搜索关键词时,你可以使用useDeferredValue
来延迟更新搜索结果的渲染,直到用户停止输入一段时间后再进行更新,这样可以避免在用户快速输入时频繁地进行渲染,提高应用的流畅性。