React hook1:useEffect
在编程中,副作用是指函数或表达式在执行过程中对外部环境产生影响的行为。例如:
- 修改外部变量(如全局变量、DOM、API 请求、设置定时器等)
什么是纯函数?
javascript
// 纯函数:输入相同,输出必定相同,不影响外部
function add(a, b) {
return a + b;
}
//有副作用的函数:影响外部状态
function updateDOM() {
document.title = "Changed!"; //副作用:修改DOM
}
React 推崇函数式编程,核心思想是:
- 组件应该是纯函数(给定相同的props和state,渲染相同的 UI)
- 副作用应该被隔离,不能直接在渲染过程中执行
React 如何处理副作用?渲染和外部处理相分离。
由于渲染必须是纯的,React 提供了useEffect , 让副作用在渲染完成后执行,而不是在渲染期间执行。
useEffect是 React 提供的副作用隔离机制,让不纯的操作(如API请求、DOM修改)不影响组件的纯渲染逻辑,同时提供清理和优化能力。
- 保持渲染纯净(在组件渲染到屏幕之后运行)
- 避免阻塞渲染
- 自动清理副作用(可以返回一个清理函数,在组件卸载时执行)
javascript
react jsx title="useEffect基础用法"
import { useEffect } from 'react';
function Example() {
useEffect(() => {
console.log('组件首次渲染后执行');
}, []); // 空依赖数组表示只运行一次
return <div>组件内容</div>;
}
javascript
react jsx title="useEffect引入依赖项监听变化"
function Example({ id }) {
useEffect(() => {
console.log('id 变化时执行:', id);
}, [id]); // 仅在 id 变化时重新执行
return <div>ID: {id}</div>;
}
javascript
react jsx title="useEffect清理副作用"
function Example() {
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器运行中...');
}, 1000);
return () => {
clearInterval(timer); // 组件卸载时清理定时器
};
}, []);
return <div>查看控制台</div>;
}
React hook2:useRef
useRef用于直接操作 DOM或存储可变值(不会触发重新渲染)。
用法1:操作DOM
javascript
import { useRef } from 'react';
function InputFocus() {
const inputRef = useRef(null); // 1. 创建 ref
const handleClick = () => {
inputRef.current.focus(); // 3. 通过 .current 操作 DOM
};
return (
<div>
{/* 2. 绑定 ref 到元素 */}
<input ref={inputRef} type="text" />
<button onClick={handleClick}>聚焦输入框</button>
</div>
);
}
用法2:存储可变值,不触发重新渲染
typescript
import { useRef, useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const renderCount = useRef(0); // 存储组件渲染次数(不会触发更新)
renderCount.current += 1; // 修改值(不会导致重新渲染)
return (
<div>
<p>Count: {count}</p>
<p>组件渲染次数: {renderCount.current}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
useRef的底层机制
javascript
// 近似实现(React 内部简化逻辑)
function useRef(initialValue) {
return { current: initialValue }; // 每次都返回同一个对象!
}
- 存储原理:React 在组件首次渲染时创建这个{ current: ... }对象,后续所有渲染都返回同一个对象(引用不变)。
- 修改current:直接修改current属性不会触发渲染,因为对象本身的引用没变,React 不会检测到变化。
为什么操作 DOM 需要绑定到标签?
html
<input ref={inputRef} />
React 的隐藏逻辑:当ref属性绑定到 DOM 标签时,React 会在渲染结束后自动将 DOM 节点赋值给inputRef.current。
javascript
// React 内部类似这样处理
const domNode = document.createElement('input');
inputRef.current = domNode; // 自动填充 DOM 节点
存储变量为何不需要绑定?
javascript
const renderCount = useRef(0);
renderCount.current += 1; // 直接修改即可
存储变量时,current只是一个普通属性,你可以自由读写,React 不会干涉它的值。
关键区别总结
场景 | 是否需要绑定到标签 | current的值来源 |
---|---|---|
操作 DOM | 需要 | 由 React 自动填充为 DOM 节点 |
存储可变值 | 不需要 | 由开发者手动赋值/修改 |
React hook3:useMemo
useMemo用于缓存计算结果,只有当依赖项变化时才重新计算,避免重复渲染时不必要的计算开销。
javascript
function UserList({ users, searchTerm }) {
// 缓存过滤后的用户列表,只有当users或searchTerm变化时才重新过滤
const filteredUsers = useMemo(() => {
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]);
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
React hook4:useNavigate
**useNavigate 就像一个提供多种导航工具的"工具箱",可以从中选择需要的功能来完成路由跳转、前进/后退、传递数据等操作。**用"工具箱"类比 useNavigate:
工具箱里的工具 | 对应 useNavigate 的功能 |
---|---|
导航地图 | navigate('/path') ------ 直接跳转到指定路径。 |
橡皮擦 | navigate('/path', { replace: true }) ------ 替换当前路由(擦掉当前记录,不保留历史)。 |
时间机器按钮 | navigate(1) 或 navigate(-1) ------ 前进或后退到历史记录中的页面。 |
快递包裹 | navigate('/path', { state: { data } }) ------ 跳转时携带隐藏数据(偷偷塞个包裹给下个页面)。 |
React hook5:useParams()
useParams从当前 URL 路径中提取参数。
示例代码:
javascript
<Route path="/user/:id" element={<UserProfile />} />
javascript
import { useParams } from 'react-router-dom';
function UserProfile() {
// 提取 URL 中的动态参数(比如 :id)
const params = useParams();
console.log(params.id); // 访问 /user/123 时,输出 "123"
return <h1>用户ID: {params.id}</h1>;
}
当用户访问 /user/123 时,useParams() 会提取出 { id: '123' }。