React中hook的用法及例子(持续更新)
1、useCallback
作用是缓存函数
例子:父组件把一个函数 handleClick
传递给一个子组件。如果不使用 useCallback
,即使 handleClick
没变,他也会在点击“切换Toggle”的时候重新渲染一遍(即打印:ChildButton: props 更新了)。
如果加上了useCallback,他就只会在依赖项count
改变的时候才重新渲染handleClick
import { useState, useCallback,useEffect } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'// 子组件:接收一个 onClick 函数
const ChildButton = ({ onClick }) => {useEffect(() => {console.log('✅ ChildButton: props 更新了')console.log('当前 onClick 函数引用:', onClick)}, [onClick]) // 🔥 依赖:只有当 onClick 引用变化时,这个 effect 才会执行return <button onClick={onClick}>我是子组件按钮</button>
}function App() {const [count, setCount] = useState(0)const [toggle, setToggle] = useState(true)// ❌ 每次渲染都会创建一个新函数(没有 useCallback)// const handleClick = () => {// alert(`当前计数: ${count}`)// }// ✅ 使用 useCallback 缓存函数// 只有当 `count` 改变时,函数才会重新创建const handleClick = useCallback(() => {alert(`当前计数: ${count}`)}, [count]) // 依赖数组:只有 count 变化时才重新生成函数return (<><div className="app"><h1>useCallback 示例</h1><div><p>计数: {count}</p><button onClick={() => setCount(count + 1)}>增加计数</button></div><div style={{ margin: '20px 0' }}><p>Toggle 状态: {toggle ? 'ON' : 'OFF'}</p><button onClick={() => setToggle(!toggle)}>切换 Toggle</button></div><hr />{/* 子组件:接收缓存的函数 */}<ChildButton onClick={handleClick} /></div></>)
}export default App
2、useMemo
作用是缓存计算结果
例子:如果不使用useMemo,每次
点击“切换Toggle”都会计算一遍slowSquare这个计算函数(即打印:正在进行昂贵的计算...)
使用useMemo后,只有count改变时候,才会计算
slowSquare这个计算函数。
import { useState, useMemo } from 'react' // ✅ 导入 useMemo
import './App.css'// 一个非常耗时的计算函数(模拟复杂逻辑)
const slowSquare = (num) => {console.log('🔍 正在进行昂贵的计算...') // 我们靠它来“看到”是否重新计算了return num * num // 返回平方(但前面的循环让它变慢)
}function App() {const [count, setCount] = useState(0)const [toggle, setToggle] = useState(true)// ❌ 每次渲染都会重新执行 slowSquare(很慢!)// const expensiveValue = slowSquare(count)// ✅ 使用 useMemo 缓存计算结果const expensiveValue = useMemo(() => {return slowSquare(count)}, [count]) // 只有当 count 变化时,才重新计算return (<><div className="app"><h1>useMemo 示例</h1><div><p>计数: {count}</p><button onClick={() => setCount(count + 1)}>增加计数</button></div><div style={{ margin: '20px 0' }}><p>Toggle 状态: {toggle ? 'ON' : 'OFF'}</p><button onClick={() => setToggle(!toggle)}>切换 Toggle(无关状态)</button></div><hr />{/* 显示缓存的计算结果 */}<p><strong>{count}</strong> 的平方是:<span style={{ color: 'blue' }}> {expensiveValue} </span></p></div></>)
}export default App
3、React.memo
作用是缓存组件
例子:如果不使用React.memo包裹子组件,每次点击点击“切换 Toggle”时,都会重新渲染子组件(即打印:ExpensiveComponent 正在渲染!)
使用React.memo包裹子组件,只有count改变时候,才会
重新渲染子组件
import React, { useState } from 'react'
import './App.css'// 子组件:模拟一个“昂贵”的组件(比如渲染大量 DOM)
const ExpensiveComponent = ({ count }) => {console.log('💥 ExpensiveComponent 正在渲染!')return (<div style={{ padding: '20px', border: '2px solid #007acc', borderRadius: '8px' }}><h3>我是昂贵的子组件</h3><p>我接收的 count 值是: <strong>{count}</strong></p></div>)
}// ✅ 使用 React.memo 包裹子组件
const MemoizedExpensiveComponent = React.memo(ExpensiveComponent)function App() {const [count, setCount] = useState(0)const [toggle, setToggle] = useState(true)return (<><div className="app" style={{ padding: '20px', fontFamily: 'Arial' }}><h1>React.memo 示例</h1><div><p>计数: {count}</p><button onClick={() => setCount(count + 1)}>增加计数(会触发子组件更新)</button></div><div style={{ margin: '30px 0' }}><p>Toggle 状态: {toggle ? 'ON' : 'OFF'}</p><button onClick={() => setToggle(!toggle)}>切换 Toggle(不会影响子组件 props)</button></div><hr />{/* 使用 memo 包裹后的组件 */}<MemoizedExpensiveComponent count={count} />{/* 没有使用 memo */}{/* <ExpensiveComponent count={count} /> */}</div></>)
}export default App
4、useContext
“跨层级”传递数据,无需prop
在React 内部维护了一个“上下文栈”,当组件调用 useContext
时,React 会:
- 检查当前渲染的“上下文环境”
- 找到对应
Context
的最新value
- 建立“订阅关系”:
Provider
→useContext
组件
通俗解释:
ThemeContext
= 一个广播电<ThemeContext.Provider value={...}>
= 电台正在播放节目useContext(ThemeContext)
= 你打开收音机,收听节目- 无论你在城市哪个角落(组件树多深),只要打开收音机,就能听到
同级创建几个组件:
父组件App.jsx
<ThemeContext.Provider>
:开始广播!
value={contextValue}
:广播的内容是 { theme: 'dark', toggleTheme: function }
// src/App.jsx
import React, { useState } from 'react'
import ThemeContext from '.ThemeContext'
import ThemeToggle from './ThemeToggle'
import DeepComponent from './DeepComponent'function App() {const [theme, setTheme] = useState('light')const toggleTheme = () => {setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'))}// 提供给所有子组件的值const contextValue = { theme, toggleTheme }return (<ThemeContext.Provider value={contextValue}><divstyle={{padding: '20px',textAlign: 'center',minHeight: '100vh',background: theme === 'dark' ? '#121212' : '#fff',color: theme === 'dark' ? '#fff' : '#000',transition: 'background-color 0.3s, color 0.3s',}}><h1>useContext 主题切换示例(组件拆分)</h1><ThemeToggle /><p>当前主题: {theme}</p><hr style={{ margin: '40px 0' }} /><DeepComponent /></div></ThemeContext.Provider>)
}export default App
ThemeContext.jsx
创建“广播电台”,即创建了一个“上下文容器”
// src/context/ThemeContext.jsx
import { createContext } from 'react'// 创建一个 Context,可以设置默认值(可选)
const ThemeContext = createContext({theme: 'light', // 默认主题toggleTheme: () => {}, // 空函数作为默认
})export default ThemeContext
ThemeToggle.jsx
收听广播 + 操作广播
import React, { useContext } from 'react'
import ThemeContext from './ThemeContext'const ThemeToggle = () => {const { theme, toggleTheme } = useContext(ThemeContext)return (<buttononClick={toggleTheme}style={{padding: '10px 20px',fontSize: '16px',background: theme === 'dark' ? '#333' : '#f0f0f0',color: theme === 'dark' ? 'white' : 'black',border: '1px solid #ccc',borderRadius: '4px',cursor: 'pointer',}}>切换到 {theme === 'dark' ? '亮色' : '暗色'} 主题</button>)
}export default ThemeToggle
DeepComponent.jsx
收听广播 + 操作广播
import React, { useContext } from 'react'
import ThemeContext from './ThemeContext'const DeepComponent = () => {const { theme } = useContext(ThemeContext)return (<divstyle={{padding: '20px',margin: '20px 0',background: theme === 'dark' ? '#1a1a1a' : '#e0e0e0',color: theme === 'dark' ? 'white' : 'black',borderRadius: '8px',}}><h3>我是深层组件</h3><p>当前主题: {theme}</p></div>)
}export default DeepComponent
结果如下: