学习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,并使用了 useState 和 useEffect 来处理数据的获取、错误处理和加载状态。
其中在第29行代码 执行了一个函数 fetchData,最后返回了一个对象数据。
所以你只要理解 自定义React Hooks 的本质就是让你写一个闭包的函数就可以了。
也就是通过函数返回一个闭包,再利用闭包来封装一段特定的逻辑,使得这些逻辑可以在函数组件中复用,并且在状态和副作用的管理上比传统类组件更加简洁和灵活。
React内置Hooks
React 中的 useState,useEffect 等 Hook 本质上也都是利用了闭包的机制来保持状态和副作用的引用。每个 Hook 都是通过闭包封装了一些逻辑,让它们在组件的生命周期内持续有效。
1. useState 是如何利用闭包的
useState 是 React 的一个非常核心的 Hook,它会返回当前状态的值和更新状态的函数。这两个返回值实际上是通过闭包实现的。具体来说:
- 状态值:React 会为每个组件实例内部维护一个状态存储,而
useState在组件每次渲染时会从这个存储中获取状态的值。 - 更新函数:
setState或setX函数也是通过闭包返回的,它会保持对该状态值的引用。当调用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 中的
useState、useEffect、useRef等 Hook 本质上依赖于闭包来保存状态、引用和副作用。闭包允许这些值在组件的生命周期内持续有效,甚至在组件重新渲染时也不会丢失。 useState:通过闭包保存状态的值和更新函数。useEffect:通过闭包来访问组件中的最新状态,并在依赖变化时触发副作用。useRef:通过闭包存储引用,允许跨渲染周期保持值。
所以,React 中的这些 Hook 都是通过闭包来实现的,它们利用了 JavaScript 的闭包特性来提供更灵活、简洁的状态管理和副作用处理机制。