# 深入探究 React:通过真实案例理解 useEffect、useRef、useMemo 和 useState React 有点像探索一座古老的城堡——迷人、充满隐藏的房间,有时还让人感到有点神秘。你一开始信心满满,以为自己已经很熟悉了。但突然间,你可能会遇到奇怪的 bug、消失的事件监听器,或者因为副作用导致的无限循环。 今天,让我们一起开启一段 React 的探险之旅。我们将通过一个实际的示例脚本,逐层揭开 `useEffect`、`useRef`、`useMemo` 和 `useState` 这些 Hook 的神秘面纱。我们会深入了解这些 Hook 如何与 DOM 交互,掌握它们的生命周期,并发现它们的实现方式如何影响你的应用性能。 装备好你的行囊,冒险者——我们要深入探索了! --- ## 示例脚本:冒险的舞台 这是我们的冒险 React 组件: ```jsx import { useEffect, useRef, useState, useMemo } from 'react'; function AdventureComponent() { const [inputValue, setInputValue] = useState(''); const [count, setCount] = useState(0); const inputRef = useRef(null); const expensiveCalculation = useMemo(() => { console.log("Calculating heavily..."); return count * 100; }, [count]); useEffect(() => { const input = inputRef.current; const handleFocus = () => console.log('Input Focused'); input.addEventListener('focus', handleFocus); return () => input.removeEventListener('focus', handleFocus); }, []); useEffect(() => { const intervalId = setInterval(() => { console.log('Interval tick:', inputValue); }, 1000); return () => clearInterval(intervalId); }, [inputValue]); return (
setInputValue(e.target.value)} />
Count: {count}
Expensive Value: {expensiveCalculation}
); } ``` 接下来,让我们逐一揭示每个决策、Hook 以及潜在的陷阱。 --- ## useState:神奇的变量守护者 ### 作用 `useState` 让你的 React 组件拥有内部状态。每当状态变化时,它会触发组件重新渲染。 ### 脚本中的示例 ```jsx const [inputValue, setInputValue] = useState(''); const [count, setCount] = useState(0); ``` 当你调用 `setInputValue` 或 `setCount` 时,React 会安排一次重新渲染。这让你的 UI 保持响应和最新。 ### 注意事项 - **频繁的状态更新:** 过于频繁的状态更新会导致性能下降,因为会引发过多的渲染。 --- ## useRef:时光穿梭的引用 ### 作用 `useRef` 提供了一个稳定的引用,可以指向 DOM 节点或在组件整个生命周期内持久存在的值,但不会引起组件重新渲染。 ### 脚本中的示例 ```jsx const inputRef = useRef(null); ``` 最初,`inputRef.current` 是 `null`。当 React 渲染完 input 元素后,`inputRef.current` 就会指向真实的 DOM 节点。 ### 生命周期 - 初始时:`inputRef.current` 为 null。 - 首次渲染后:引用实际的 DOM 元素。 - 组件卸载时:React 会自动清理,ref 会变回 null。 ### 注意事项 - **直接操作 DOM:** 只有在 React 不管理该部分时才安全。通过 ref 绑定监听器时要确保不会操作过时的 DOM 节点。 --- ## useEffect:副作用的掌控者 ### 作用 用于处理副作用,比如事件监听、网络请求或定时器。默认在每次渲染后执行,除非依赖数组另有规定。 ### 脚本中的示例 ```jsx useEffect(() => { const input = inputRef.current; const handleFocus = () => console.log('Input Focused'); input.addEventListener('focus', handleFocus); return () => input.removeEventListener('focus', handleFocus); }, []); ``` 由于依赖数组为空,这段代码只会在初次渲染后执行一次,把事件监听器绑定到 input 上。 ### 清理模式 - React 会在重新执行 effect 或组件卸载前,调用返回的清理函数。务必在这里移除监听器或定时器,避免内存泄漏。 ### 注意事项 - **无限循环:** 忽略或错误使用依赖项可能导致副作用无限循环执行。 - **闭包过时:** 忘记添加依赖项可能导致 effect 使用了过期的值。 --- ## useMemo:记住昂贵的计算结果 ### 作用 缓存耗时的计算结果,仅在依赖项变化时重新计算。 ### 脚本中的示例 ```jsx const expensiveCalculation = useMemo(() => { console.log("Calculating heavily..."); return count * 100; }, [count]); ``` 只有当 `count` 变化时,这个计算才会重新执行。否则,React 会复用缓存的值,节省 CPU 资源。 ### 注意事项 - **过度使用:** 滥用 useMemo 反而可能带来额外开销,而不是优化。 --- ## 原生 DOM API 与 React:朋友还是对手? 原生 DOM 方法(如 `document.getElementById`、`addEventListener`)很强大,但 React 有自己的一套 DOM 管理方式。 ### 何时安全 - 全局事件(如 window resize) - 通过 ref 绑定在稳定、非 React 管理的 DOM 节点上的事件监听 ### 何时危险 - 直接操作由 React 管理的 DOM 元素(可能导致不一致和 bug) ### 示例(危险用法) ```jsx useEffect(() => { const el = document.getElementById('my-input'); el.addEventListener('focus', handler); }, []); ``` 如果 React 之后替换了该 DOM 节点,你的事件监听器就会失效,导致难以发现的 bug。 --- ## 性能与优化建议 - 在 `useEffect` 和 `useMemo` 中始终正确填写依赖数组。 - 尽量减少状态更新,能批量更新时就批量。 - 用 `useRef` 避免不必要的重新渲染。 --- ## 总结:你的 React 冒险才刚刚开始 深入理解 React Hooks,就像拥有了一张可靠的冒险地图。它能帮助你解决 bug、优化性能,并写出简洁、健壮的代码。 记住,React 并不是魔法——它只是设计得很巧妙。当你掌握了这些细节后,你会发现 React 既有趣又不再神秘。你的 React 冒险才刚刚开始,继续探索、保持好奇,每一步都会让你看得更清楚。