9. useState 和 useEffect 的执行时机和细节?
答案:
useState 的执行时机:
1. 初始化阶段
function MyComponent() {
// 组件首次渲染时执行
const [count, setCount] = useState(0) // 初始化:count = 0
const [name, setName] = useState('React') // 初始化:name = 'React'
return <div>{count}</div>
}
2. 更新阶段
function MyComponent() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1) // 触发重新渲染
setCount(count + 1) // 不会立即更新,仍然是旧值
}
return <button onClick={handleClick}>{count}</button>
}
3. 函数式更新
function MyComponent() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount((prevCount) => prevCount + 1) // 使用函数式更新
setCount((prevCount) => prevCount + 1) // 基于最新值更新
}
return <button onClick={handleClick}>{count}</button>
}
useEffect 的执行时机:
1. 组件挂载后
function MyComponent() {
const [data, setData] = useState(null)
useEffect(() => {
// 组件挂载后执行
console.log('组件已挂载')
fetchData().then(setData)
}, []) // 空依赖数组,只在挂载时执行
return <div>{data ? data.title : 'Loading...'}</div>
}
2. 每次渲染后
function MyComponent({ userId }) {
const [user, setUser] = useState(null)
useEffect(() => {
// 每次渲染后都会执行
console.log('组件已渲染')
fetchUser(userId).then(setUser)
}) // 没有依赖数组,每次渲染都执行
return <div>{user ? user.name : 'Loading...'}</div>
}
3. 依赖项变化时
function MyComponent({ userId }) {
const [user, setUser] = useState(null)
useEffect(() => {
// 只有当 userId 变化时才执行
console.log('userId 变化了:', userId)
fetchUser(userId).then(setUser)
}, [userId]) // 依赖 userId
return <div>{user ? user.name : 'Loading...'}</div>
}
执行顺序:
1. 组件渲染顺序
function MyComponent() {
console.log('1. 组件开始渲染')
const [count, setCount] = useState(0)
console.log('2. useState 执行')
useEffect(() => {
console.log('4. useEffect 执行(渲染后)')
})
console.log('3. 组件渲染完成')
return <div>{count}</div>
}
// 输出顺序:
// 1. 组件开始渲染
// 2. useState 执行
// 3. 组件渲染完成
// 4. useEffect 执行(渲染后)
2. 多个 useEffect 的执行顺序
function MyComponent() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log('useEffect 1')
})
useEffect(() => {
console.log('useEffect 2')
}, [])
useEffect(() => {
console.log('useEffect 3')
}, [count])
// 执行顺序:按照声明顺序执行
}
清理函数:
function MyComponent() {
const [count, setCount] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setCount((c) => c + 1)
}, 1000)
// 清理函数:组件卸载或依赖变化前执行
return () => {
clearInterval(timer)
console.log('定时器已清理')
}
}, [])
return <div>{count}</div>
}
异步操作:
function MyComponent() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
const fetchData = async () => {
try {
setLoading(true)
const result = await api.getData()
// 检查组件是否已卸载
if (!cancelled) {
setData(result)
}
} catch (error) {
if (!cancelled) {
console.error('获取数据失败:', error)
}
} finally {
if (!cancelled) {
setLoading(false)
}
}
}
fetchData()
return () => {
cancelled = true // 取消请求
}
}, [])
return <div>{loading ? 'Loading...' : data?.title}</div>
}
性能优化:
function MyComponent({ items }) {
const [filteredItems, setFilteredItems] = useState([])
// 使用 useMemo 避免不必要的计算
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0)
}, [items])
// 使用 useCallback 避免不必要的重新创建
const handleFilter = useCallback(
(filter) => {
setFilteredItems(items.filter((item) => item.category === filter))
},
[items]
)
useEffect(() => {
// 只在 items 变化时执行
setFilteredItems(items)
}, [items])
return <div>{filteredItems.length} items</div>
}