大白话React Hooks(如 useState、useEffect)的使用方法与原理

啥是 React Hooks

在 React 里,以前我们写组件主要用类(class)的方式,写起来有点复杂,尤其是处理状态和副作用的时候。React Hooks 就是 React 16.8 之后推出的新特性,它能让我们不用写类,直接在函数组件里使用状态和其他 React 特性,让代码更简洁、更易复用。

useState 的使用方法与原理

使用方法

useState 就像是给函数组件装了个"小抽屉",可以用来存放和管理状态。下面是一个简单的计数器示例:

jsx 复制代码
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 等,这些操作就叫做副作用。下面是一个简单的数据获取示例:

jsx 复制代码
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类组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 这几个生命周期函数的组合。可以用来处理一些副作用操作,像发送网络请求获取数据、添加订阅、操作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 来延迟更新搜索结果的渲染,直到用户停止输入一段时间后再进行更新,这样可以避免在用户快速输入时频繁地进行渲染,提高应用的流畅性。
相关推荐
yunvwugua__几秒前
Python训练营打卡 Day26
前端·javascript·python
满怀10158 分钟前
【Django全栈开发实战】从零构建企业级Web应用
前端·python·django·orm·web开发·前后端分离
Darling02zjh1 小时前
GUI图形化演示
前端
Channing Lewis1 小时前
如何判断一个网站后端是用什么语言写的
前端·数据库·python
互联网搬砖老肖1 小时前
Web 架构之状态码全解
前端·架构
showmethetime1 小时前
matlab提取脑电数据的五种频域特征指标数值
前端·人工智能·matlab
码农捻旧2 小时前
解决Mongoose “Cannot overwrite model once compiled“ 错误的完整指南
javascript·数据库·mongodb·node.js·express
淡笑沐白2 小时前
探索Turn.js:打造惊艳的3D翻页效果
javascript·html5·turn.js
海上彼尚2 小时前
秒删node_modules[无废话版]
vue.js·react.js
程序猿阿伟2 小时前
《数字分身进化论:React Native与Flutter如何打造沉浸式虚拟形象编辑》
flutter·react native·react.js