学习React Hooks基本原理 - 致于学会封装独立功能Hooks

259 阅读5分钟

学习React Hooks基本原理 - 致于学会封装独立功能Hooks

介绍 React Hooks 的基本原理,
重点分析了React 内置 Hooks(如 useState、useEffect、useRef)如何利用闭包管理组件的状态和副作用,确保数据在组件内持久化。

我让ChatGPT帮我封装一个单独功能的Hooks

得到这样的一个自定义Hooks

自定义 Hook:useFetch

这个 Hook 用来从指定的 URL 获取数据,可以传递 URL 和其他可选的配置参数,返回数据、加载状态和错误信息。

import { useState, useEffect } from 'react';

const useFetch = (url, options = {}) => {
  // 使用 useState 管理数据、加载状态和错误状态
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 异步请求数据的函数
    const fetchData = async () => {
      setLoading(true);  // 设置加载状态为 true
      setError(null);    // 重置错误信息

      try {
        const response = await fetch(url, options);
        if (!response.ok) {
          throw new Error('网络请求失败');
        }
        const result = await response.json();
        setData(result);  // 设置返回的数据
      } catch (err) {
        setError(err.message);  // 设置错误信息
      } finally {
        setLoading(false);  // 设置加载状态为 false
      }
    };

    fetchData();
  }, [url, options]);  // 当 URL 或 options 改变时重新执行

  return { data, loading, error };
};

export default useFetch;

从以上它所生成的Hooks中能了解到,这是一个网络请求功能的自定义 React Hook,并使用了 useStateuseEffect 来处理数据的获取、错误处理和加载状态。

其中在第29行代码 执行了一个函数 fetchData,最后返回了一个对象数据。

所以你只要理解 自定义React Hooks 的本质就是让你写一个闭包的函数就可以了。

也就是通过函数返回一个闭包,再利用闭包来封装一段特定的逻辑,使得这些逻辑可以在函数组件中复用,并且在状态和副作用的管理上比传统类组件更加简洁和灵活。

React内置Hooks

React 中的 useStateuseEffect 等 Hook 本质上也都是利用了闭包的机制来保持状态和副作用的引用。每个 Hook 都是通过闭包封装了一些逻辑,让它们在组件的生命周期内持续有效。

1. useState 是如何利用闭包的

useState 是 React 的一个非常核心的 Hook,它会返回当前状态的值和更新状态的函数。这两个返回值实际上是通过闭包实现的。具体来说:

  • 状态值:React 会为每个组件实例内部维护一个状态存储,而 useState 在组件每次渲染时会从这个存储中获取状态的值。
  • 更新函数setStatesetX 函数也是通过闭包返回的,它会保持对该状态值的引用。当调用 setState 时,它实际上会更新这个闭包中存储的状态值,并触发组件的重新渲染。

举个简单的例子:

import React, { useState } from 'react';

const Counter = () => {
  // 使用 useState 返回闭包中的状态和更新函数
  const [count, setCount] = useState(0);

  const increment = () => {
    // 更新状态时,setCount 使用了闭包来更新 `count` 的值
    setCount(count + 1);
  };

  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={increment}>增加计数</button>
    </div>
  );
};

export default Counter;

闭包的作用setCount 访问和更新的 count 是在组件的闭包中存储的。在每次点击按钮时,count 会通过闭包捕获并更新,这样 React 可以保持对当前状态的引用,即使组件重新渲染,setCount 依然会正确地更新该值。

2. useEffect 是如何利用闭包的

useEffect 主要用于处理副作用(比如网络请求、订阅、手动 DOM 操作等)。它也利用了闭包的机制来管理副作用的逻辑。

当你传入 useEffect 一个函数时,这个函数会在组件渲染后被调用,并且会访问组件内的状态和 props。这里面用到了闭包,因为即使组件重新渲染,useEffect 里的回调函数依然能访问到最新的状态和 props。

例如:

import React, { useState, useEffect } from 'react';

const Example = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // 这个副作用使用了闭包,可以访问最新的 `count` 状态
    document.title = `当前计数: ${count}`;
  }, [count]); // 依赖 `count`,只有当 `count` 改变时才会执行副作用

  const increment = () => {
    setCount(count + 1);  // 更新 `count` 时触发重新渲染
  };

  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={increment}>增加计数</button>
    </div>
  );
};

export default Example;

闭包的作用useEffect 中的回调函数会捕获最新的 count 状态,这样每次 count 更新时,useEffect 内的回调函数会使用到最新的状态。

如果 useEffect 的依赖项没有变化,它就不会重新执行副作用(例如上面的 count)。但即便是 count 发生变化,useEffect 依然会利用闭包来访问和更新该状态。

3. useRef 是如何利用闭包的

useRef 也可以视为利用闭包的一种方式,它通常用来存储跨渲染周期的引用(例如 DOM 元素的引用或者某个值)。通过 useRef 创建的对象可以在组件重新渲染时保持其引用。

import React, { useState, useRef } from 'react';

const Timer = () => {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);

  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      setCount((prevCount) => prevCount + 1);  // 依赖闭包中的 `prevCount`
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };

  return (
    <div>
      <p>计时器: {count}</p>
      <button onClick={startTimer}>开始计时</button>
      <button onClick={stopTimer}>停止计时</button>
    </div>
  );
};

export default Timer;

闭包的作用:在 setInterval 中,回调函数依赖于 count 状态。这里 prevCount 是通过闭包捕获的,因此每次更新 count 时,setCount 会在闭包中拿到上一次的状态,从而进行正确的计算。

总结

  • 闭包的关键作用:React 中的 useStateuseEffectuseRef 等 Hook 本质上依赖于闭包来保存状态、引用和副作用。闭包允许这些值在组件的生命周期内持续有效,甚至在组件重新渲染时也不会丢失。
  • useState:通过闭包保存状态的值和更新函数。
  • useEffect:通过闭包来访问组件中的最新状态,并在依赖变化时触发副作用。
  • useRef:通过闭包存储引用,允许跨渲染周期保持值。

所以,React 中的这些 Hook 都是通过闭包来实现的,它们利用了 JavaScript 的闭包特性来提供更灵活、简洁的状态管理和副作用处理机制。