React Hooks 知识点详解
编程小白也能看懂的React Hooks入门指南
什么是React Hooks?
React Hooks是React 16.8引入的新特性,它允许你在不编写类组件的情况下使用state和其他React特性。
简单来说,Hooks就是一些特殊的函数,让你能够”钩入”React的功能。
为什么需要Hooks? 以前使用类组件时,逻辑分散在各个生命周期方法中,代码难以复用和维护。Hooks让函数组件拥有了类组件的能力,同时使代码更简洁、更易复用。
1
useState – 状态管理
⚖️
通俗解释
useState就像给函数组件添加了一个记忆功能。它让函数组件能够记住一些信息(状态),当这些信息改变时,组件会自动重新渲染。
import { useState } from ‘react’;
function Counter() {
// count是状态值,setCount是更新状态的函数
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
function Counter() {
// count是状态值,setCount是更新状态的函数
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
使用场景: 当你需要在组件中存储和更新数据时使用,比如表单输入、计数器、开关状态等。
注意: useState的初始值只在组件首次渲染时使用,后续重新渲染时会使用最新的状态值。
2
useEffect – 副作用处理
🔍
通俗解释
useEffect就像组件的”后厨”,它让你在组件渲染后执行一些额外的操作(副作用),比如数据获取、订阅事件、手动修改DOM等。
import { useState, useEffect } from ‘react’;
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// 当userId改变时,获取用户数据
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
};
fetchUser();
// 清理函数(可选)
return () => {
console.log(‘清理工作’);
};
}, [userId]); // 依赖数组
return <div>{user ? user.name : ‘Loading…’}</div>;
}
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// 当userId改变时,获取用户数据
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
};
fetchUser();
// 清理函数(可选)
return () => {
console.log(‘清理工作’);
};
}, [userId]); // 依赖数组
return <div>{user ? user.name : ‘Loading…’}</div>;
}
三种使用模式:
useEffect(() => {...})
– 每次渲染后都执行useEffect(() => {...}, [])
– 仅在组件挂载时执行一次useEffect(() => {...}, [dep1, dep2])
– 当依赖项改变时执行
注意事项:
1. 避免在useEffect中执行阻塞渲染的操作
2. 如果依赖项是对象或数组,注意它们的引用变化可能导致useEffect频繁执行
2. 如果依赖项是对象或数组,注意它们的引用变化可能导致useEffect频繁执行
3
useContext – 共享数据
📡
通俗解释
useContext就像组件间的”广播系统”,它让你在组件树中传递数据,避免了层层传递props的麻烦。
// 1. 创建Context
const ThemeContext = React.createContext(‘light’);
// 2. 提供Context值
function App() {
return (
<ThemeContext.Provider value=”dark”>
<Toolbar />
</ThemeContext.Provider>
);
}
// 3. 在子组件中使用Context
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>当前主题: {theme}</div>;
}
const ThemeContext = React.createContext(‘light’);
// 2. 提供Context值
function App() {
return (
<ThemeContext.Provider value=”dark”>
<Toolbar />
</ThemeContext.Provider>
);
}
// 3. 在子组件中使用Context
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>当前主题: {theme}</div>;
}
使用场景:
主题切换、用户认证信息、多语言切换、全局状态管理等需要跨组件共享数据的场景。
注意: 当Context的值变化时,所有使用该Context的组件都会重新渲染,即使它们只使用了部分值。
4
useRef – 引用DOM和持久化值
📌
通俗解释
useRef就像给你的组件一个”储物柜”,可以存放一些东西,这些东西在组件重新渲染时不会丢失,而且改变它们不会触发重新渲染。
import { useRef, useEffect } from ‘react’;
function TextInput() {
// 创建一个ref来存储input DOM元素
const inputRef = useRef(null);
// 存储一个不会导致重新渲染的值
const renderCount = useRef(0);
useEffect(() => {
// 组件挂载后自动聚焦到输入框
inputRef.current.focus();
// 更新渲染次数(不会触发重新渲染)
renderCount.current = renderCount.current + 1;
}, []);
return (
<div>
<input ref={inputRef} type=”text” />
<p>组件渲染次数: {renderCount.current}</p>
</div>
);
}
function TextInput() {
// 创建一个ref来存储input DOM元素
const inputRef = useRef(null);
// 存储一个不会导致重新渲染的值
const renderCount = useRef(0);
useEffect(() => {
// 组件挂载后自动聚焦到输入框
inputRef.current.focus();
// 更新渲染次数(不会触发重新渲染)
renderCount.current = renderCount.current + 1;
}, []);
return (
<div>
<input ref={inputRef} type=”text” />
<p>组件渲染次数: {renderCount.current}</p>
</div>
);
}
主要用途:
- 访问DOM元素(如聚焦输入框、获取元素尺寸)
- 存储可变值而不触发重新渲染
- 保存上一次的状态或props
注意: 修改useRef的current属性不会触发重新渲染。如果需要根据变化触发渲染,应该使用useState。
5
useMemo – 记忆计算结果
💾
通俗解释
useMemo就像给你的计算器加了一个”缓存功能”,它记住复杂计算的结果,只有当依赖项改变时才会重新计算,避免不必要的重复计算。
import { useMemo, useState } from ‘react’;
function ExpensiveComponent({ list }) {
const [filter, setFilter] = useState(”);
// 只有list或filter改变时,才会重新计算filteredList
const filteredList = useMemo(() => {
console.log(‘重新计算列表’);
// 假设这是一个耗时的过滤操作
return list.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [list, filter]);
return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
/>
{filteredList.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
function ExpensiveComponent({ list }) {
const [filter, setFilter] = useState(”);
// 只有list或filter改变时,才会重新计算filteredList
const filteredList = useMemo(() => {
console.log(‘重新计算列表’);
// 假设这是一个耗时的过滤操作
return list.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [list, filter]);
return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
/>
{filteredList.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
何时使用:
- 计算成本高的操作(如大型列表过滤、复杂计算)
- 避免子组件不必要的重新渲染
- 依赖项变化时才需要重新计算的值
注意: 不要滥用useMemo,因为记忆化本身也有开销。只在性能确实需要优化时才使用。
6
useCallback – 记忆函数
📞
通俗解释
useCallback就像给你的函数一个”身份证”,它记住函数本身,只有当依赖项改变时才返回新的函数,避免子组件不必要的重新渲染。
import { useState, useCallback } from ‘react’;
function ParentComponent() {
const [count, setCount] = useState(0);
const [value, setValue] = useState(”);
// 只有count改变时才会返回新的increment函数
const increment = useCallback(() => {
setCount(c => c + 1);
}, []); // 空依赖数组表示函数永远不会改变
// 只有当value改变时才会返回新的handleChange函数
const handleChange = useCallback((e) => {
setValue(e.target.value);
}, []);
return (
<div>
<Child onIncrement={increment} />
<input value={value} onChange={handleChange} />
<p>计数: {count}</p>
</div>
);
}
// 使用React.memo避免不必要的重新渲染
const Child = React.memo(({ onIncrement }) => {
console.log(‘子组件渲染’);
return <button onClick={onIncrement}>增加</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [value, setValue] = useState(”);
// 只有count改变时才会返回新的increment函数
const increment = useCallback(() => {
setCount(c => c + 1);
}, []); // 空依赖数组表示函数永远不会改变
// 只有当value改变时才会返回新的handleChange函数
const handleChange = useCallback((e) => {
setValue(e.target.value);
}, []);
return (
<div>
<Child onIncrement={increment} />
<input value={value} onChange={handleChange} />
<p>计数: {count}</p>
</div>
);
}
// 使用React.memo避免不必要的重新渲染
const Child = React.memo(({ onIncrement }) => {
console.log(‘子组件渲染’);
return <button onClick={onIncrement}>增加</button>;
});
使用场景:
- 将函数传递给子组件时(特别是使用React.memo优化的组件)
- 作为useEffect的依赖项时
- 大型列表中的事件处理函数
注意: useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。两者区别在于useMemo返回计算值,useCallback返回函数本身。
useMemo vs useCallback
这两个Hook容易混淆,下面是对比说明:
useMemo
用途: 记忆计算结果
返回值: 计算得到的值
示例:
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);
() => computeExpensiveValue(a, b),
[a, b]
);
useCallback
用途: 记忆函数本身
返回值: 函数
示例:
const memoizedCallback = useCallback(
() => { doSomething(a, b); },
[a, b]
);
() => { doSomething(a, b); },
[a, b]
);
关系
useCallback(fn, deps) 等同于:
useMemo(
() => fn,
deps
);
() => fn,
deps
);
两者都是性能优化手段,避免不必要的计算或渲染。
自定义Hooks – 打造你自己的Hook
通俗解释
自定义Hook就像创建你自己的”工具包”,把组件间的共享逻辑提取出来,变成可复用的函数。自定义Hook是名字以”use”开头的函数。
// 自定义Hook:使用窗口宽度
import { useState, useEffect } from ‘react’;
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener(‘resize’, handleResize);
// 清理函数
return () => {
window.removeEventListener(‘resize’, handleResize);
};
}, []);
return width;
}
// 在组件中使用自定义Hook
function ResponsiveComponent() {
const width = useWindowWidth();
return <div>窗口宽度: {width}px</div>;
}
import { useState, useEffect } from ‘react’;
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener(‘resize’, handleResize);
// 清理函数
return () => {
window.removeEventListener(‘resize’, handleResize);
};
}, []);
return width;
}
// 在组件中使用自定义Hook
function ResponsiveComponent() {
const width = useWindowWidth();
return <div>窗口宽度: {width}px</div>;
}
自定义Hook的好处:
- 逻辑复用 – 多个组件共享相同逻辑
- 代码组织 – 将复杂逻辑拆分为小函数
- 可测试性 – 独立于组件的逻辑更容易测试
注意:
1. 自定义Hook必须以”use”开头
2. 每个组件使用Hook时都有独立的状态
3. 可以在自定义Hook中使用其他Hook
2. 每个组件使用Hook时都有独立的状态
3. 可以在自定义Hook中使用其他Hook