React 18.3 官方 Hooks 详解
React 18.3 引入了多个官方 Hooks,这些 Hooks 使得在函数组件中管理状态和副作用变得更加简洁和高效。本文将详细介绍所有官方 Hooks,包括其用法、示例代码以及实际应用场景。
目录
- [基础 Hooks](#基础 Hooks "#%E5%9F%BA%E7%A1%80-hooks")
- [额外 Hooks](#额外 Hooks "#%E9%A2%9D%E5%A4%96-hooks")
- [自定义 Hooks](#自定义 Hooks "#%E8%87%AA%E5%AE%9A%E4%B9%89-hooks")
- [Hooks 使用示例](#Hooks 使用示例 "#hooks-%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B")
- 总结
基础 Hooks
useState
useState
是最基本的 Hook,用于在函数组件中添加状态。
语法
javascript
const [state, setState] = useState(initialState);
- state:当前的状态值。
- setState:更新状态的函数。
- initialState:状态的初始值。
示例
javascript
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>当前计数:{count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
export default Counter;
详细解释
在上面的示例中:
- 导入 Hook :从
react
中导入useState
。 - 初始化状态 :调用
useState(0)
初始化count
为0
,同时获取setCount
方法用于更新状态。 - 更新状态 :
increment
函数使用setCount
来更新count
的值,每次点击按钮时,count
增加1
。 - 渲染 :组件渲染当前的
count
值,并提供一个按钮触发increment
函数。
useEffect
useEffect
用于在函数组件中执行副作用操作,如数据获取、订阅、手动更改 DOM 等。
语法
javascript
useEffect(() => {
// 副作用逻辑
return () => {
// 清理逻辑
};
}, [dependencies]);
- 副作用逻辑:在组件渲染后执行的代码。
- 清理逻辑:在组件卸载或依赖项变化前执行的清理代码。
- dependencies:依赖项数组,控制副作用的执行时机。
示例
javascript
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
// 清理定时器
return () => clearInterval(interval);
}, []);
return (
<div>
<p>已运行 {seconds} 秒</p>
</div>
);
}
export default Timer;
详细解释
- 初始化状态 :
seconds
初始化为0
。 - 设置副作用 :
useEffect
中设置一个定时器,每秒更新一次seconds
。 - 清理副作用:返回一个函数,清除定时器,防止内存泄漏。
- 依赖项数组 :传递空数组
[]
,意味着此副作用仅在组件挂载和卸载时执行一次。
useContext
useContext
用于在函数组件中使用 React 上下文(Context),避免通过层层传递 props。
语法
javascript
const contextValue = useContext(Context);
- Context :通过
React.createContext
创建的上下文对象。 - contextValue:上下文的当前值。
示例
javascript
import React, { useContext } from 'react';
// 创建上下文
const ThemeContext = React.createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button className={theme}>
当前主题:{theme}
</button>
);
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}
export default App;
详细解释
- 创建上下文 :使用
React.createContext
创建ThemeContext
,默认值为'light'
。 - 使用上下文 :在
ThemedButton
组件中,通过useContext(ThemeContext)
获取当前上下文的值。 - 提供上下文 :在
App
组件中,使用ThemeContext.Provider
提供'dark'
作为上下文值。 - 渲染 :
ThemedButton
显示当前主题为'dark'
。
额外 Hooks
useReducer
useReducer
是 useState
的替代方案,适用于复杂的状态逻辑和多个状态值之间的依赖。
语法
javascript
const [state, dispatch] = useReducer(reducer, initialArg, init);
- reducer :纯函数
(state, action) => newState
。 - initialArg:初始状态值。
- init:可选的初始化函数。
示例
javascript
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>计数:{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+1</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
</div>
);
}
export default Counter;
详细解释
- 定义初始状态 :
initialState
为{ count: 0 }
。 - 定义 reducer :根据
action.type
更改状态。 - 使用 Hook :
useReducer(reducer, initialState)
返回当前状态和dispatch
方法。 - 分发动作 :通过调用
dispatch
方法发送动作对象来更新状态。
useCallback
useCallback
返回一个记忆化的回调函数,避免在每次渲染时创建新的函数实例,优化性能。
语法
javascript
const memoizedCallback = useCallback(() => {
// 回调逻辑
}, [dependencies]);
- 回调逻辑:需要记忆化的函数。
- dependencies:依赖项数组,控制回调函数的重新创建。
示例
javascript
import React, { useState, useCallback } from 'react';
import Child from './Child';
function Parent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<p>父组件计数:{count}</p>
<Child onIncrement={increment} />
</div>
);
}
export default Parent;
javascript
// src/view/Child.js
import React from 'react';
function Child({ onIncrement }) {
console.log('Child 重新渲染');
return (
<button onClick={onIncrement}>子组件 +1</button>
);
}
export default React.memo(Child);
详细解释
- 定义回调函数 :在
Parent
组件中,使用useCallback
记忆化increment
函数。 - 传递给子组件 :将
increment
作为onIncrement
属性传递给Child
子组件。 - 优化子组件 :
Child
使用React.memo
包裹,只有在onIncrement
改变时重新渲染。 - 避免不必要渲染 :由于
increment
是记忆化的,Child
组件不会因为父组件的其他状态变化而重新渲染。
useMemo
useMemo
返回一个记忆化的值,避免在每次渲染时执行高开销的计算。
语法
javascript
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 计算逻辑:需要记忆化的计算函数。
- dependencies:依赖项数组,控制计算函数的重新执行。
示例
javascript
import React, { useState, useMemo } from 'react';
function ExpensiveComponent({ num }) {
const computeFactorial = (n) => {
console.log('计算阶乘');
return n <= 0 ? 1 : n * computeFactorial(n - 1);
};
const factorial = useMemo(() => computeFactorial(num), [num]);
return (
<div>
<p>{num} 的阶乘是 {factorial}</p>
</div>
);
}
function App() {
const [number, setNumber] = useState(1);
const [text, setText] = useState('');
return (
<div>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
/>
<ExpensiveComponent num={number} />
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="输入文本"
/>
<p>输入内容:{text}</p>
</div>
);
}
export default App;
详细解释
- 定义计算函数 :
computeFactorial
计算阶乘,并在每次计算时打印日志。 - 使用 useMemo :
useMemo
缓存computeFactorial(num)
的结果,只有当num
变化时才重新计算。 - 优化性能 :在
App
组件中,输入文本text
改变时,ExpensiveComponent
不会重新计算阶乘,避免不必要的开销。
useRef
useRef
返回一个可变的 Ref 对象,可以在整个组件生命周期内保持不变,常用于访问 DOM 元素或保持可变变量。
语法
javascript
const refContainer = useRef(initialValue);
- initialValue:Ref 的初始值。
- refContainer :包含
.current
属性的对象。
示例
javascript
import React, { useRef } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input ref={inputRef} type="text" placeholder="输入文本" />
<button onClick={focusInput}>聚焦输入框</button>
</div>
);
}
export default TextInput;
详细解释
- 创建 Ref :使用
useRef(null)
创建一个 Ref 对象inputRef
,初始值为null
。 - 绑定 Ref :将
inputRef
绑定到<input>
元素上,通过ref={inputRef}
。 - 访问 DOM 元素 :
focusInput
函数通过inputRef.current
访问实际的 DOM 元素,并调用focus()
方法聚焦输入框。 - 保持引用 :
inputRef
在组件的整个生命周期内保持不变,不会因重新渲染而丢失。
useImperativeHandle
useImperativeHandle
与 forwardRef
一起使用,允许自定义暴露给父组件的实例值。
语法
javascript
useImperativeHandle(ref, () => ({
// 暴露的方法或属性
}), [dependencies]);
- ref :来自
forwardRef
的 Ref 对象。 - 返回对象:父组件可以访问的属性或方法。
- dependencies:依赖项数组,控制实例值的更新。
示例
javascript
import React, { useImperativeHandle, useRef, forwardRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
}
}));
return <input ref={inputRef} type="text" />;
});
function Parent() {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>聚焦输入框</button>
<button onClick={() => inputRef.current.clear()}>清空输入框</button>
</div>
);
}
export default Parent;
详细解释
- 创建子组件 :
CustomInput
使用forwardRef
接收父组件传递的ref
。 - 使用 useImperativeHandle :在子组件中,
useImperativeHandle
定义了focus
和clear
方法,暴露给父组件。 - 父组件调用方法 :在
Parent
组件中,通过inputRef.current.focus()
和inputRef.current.clear()
调用子组件暴露的方法。
useLayoutEffect
useLayoutEffect
与 useEffect
类似,但在 DOM 更新后同步执行,适用于需要立即读取或同步更改 DOM 的情况。
语法
javascript
useLayoutEffect(() => {
// 副作用逻辑
return () => {
// 清理逻辑
};
}, [dependencies]);
- 副作用逻辑:在 DOM 更新后立即执行。
- 清理逻辑:在依赖项变化或组件卸载前执行。
- dependencies:依赖项数组,控制副作用的执行时机。
示例
javascript
import React, { useState, useLayoutEffect, useRef } from 'react';
function LayoutEffectDemo() {
const [width, setWidth] = useState(0);
const divRef = useRef();
useLayoutEffect(() => {
const divWidth = divRef.current.getBoundingClientRect().width;
setWidth(divWidth);
}, []);
return (
<div>
<div ref={divRef} style={{ width: '50%' }}>
这个 div 的宽度是父容器的 50%
</div>
<p>div 宽度:{width}px</p>
</div>
);
}
export default LayoutEffectDemo;
详细解释
- 创建 Ref :
divRef
用于引用<div>
元素。 - 使用 useLayoutEffect :在 DOM 更新后立即获取
<div>
的宽度,并更新状态width
。 - 渲染宽度 :显示获取到的
<div>
宽度值。 - 不同于 useEffect :
useLayoutEffect
中的代码会在浏览器绘制前同步执行,适合需要在渲染前读取布局的情况。
useDebugValue
useDebugValue
用于在 React DevTools 中显示自定义 Hook 的调试信息。
语法
javascript
useDebugValue(value);
- value:要在 DevTools 中显示的值。
示例
javascript
import React, { useState, useEffect, useDebugValue } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// 假设订阅好友状态的方法
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
}, [friendID]);
useDebugValue(isOnline ? '在线' : '离线');
return isOnline;
}
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return '加载中...';
}
return isOnline ? '在线' : '离线';
}
export default FriendStatus;
详细解释
- 定义自定义 Hook :
useFriendStatus
用于订阅好友的在线状态。 - 使用 useDebugValue :将
isOnline
状态转换为'在线'
或'离线'
,便于在 DevTools 中查看。 - 查看调试信息 :在 React DevTools 中,开发者可以看到
useFriendStatus
Hook 的当前状态值。
自定义 Hooks
自定义 Hooks 允许你封装可复用的逻辑,使组件更加简洁和可维护。
示例:使用自定义 Hook 处理表单输入
javascript
import React, { useState } from 'react';
// 定义自定义 Hook
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
const handleChange = (e) => {
setValue(e.target.value);
};
return {
value,
onChange: handleChange
};
}
function Form() {
const name = useFormInput('');
const email = useFormInput('');
const handleSubmit = (e) => {
e.preventDefault();
console.log('姓名:', name.value);
console.log('邮箱:', email.value);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>姓名:</label>
<input type="text" {...name} />
</div>
<div>
<label>邮箱:</label>
<input type="email" {...email} />
</div>
<button type="submit">提交</button>
</form>
);
}
export default Form;
详细解释
- 定义自定义 Hook :
useFormInput
管理输入值和变化处理。 - 使用 Hook :在
Form
组件中,分别为姓名
和邮箱
创建输入状态。 - 绑定输入 :通过展开运算符
{...name}
和{...email}
将value
和onChange
绑定到<input>
元素。 - 提交表单:在提交时,打印输入的值。
Hooks 使用示例
以下是一个综合示例,展示如何结合使用多个 Hooks 来构建一个功能丰富的组件。
javascript
// src/hooks/useFetch.js
import { useState, useEffect, useDebugValue } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
setLoading(true);
fetch(url)
.then(response => response.json())
.then(json => {
if (isMounted) {
setData(json);
setLoading(false);
}
})
.catch(err => {
if (isMounted) {
setError(err);
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, [url]);
useDebugValue(data ? '数据加载完成' : '加载中');
return { data, loading, error };
}
export default useFetch;
javascript
// src/view/UserList.js
import React, { useState, useCallback } from 'react';
import useFetch from '../hooks/useFetch';
function UserList() {
const [url, setUrl] = useState('https://jsonplaceholder.typicode.com/users');
const { data, loading, error } = useFetch(url);
const refresh = useCallback(() => {
setUrl('https://jsonplaceholder.typicode.com/users');
}, []);
if (loading) return <p>加载中...</p>;
if (error) return <p>发生错误:{error.message}</p>;
return (
<div>
<h1>用户列表</h1>
<button onClick={refresh}>刷新</button>
<ul>
{data.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
}
export default UserList;
详细解释
-
自定义 Hook
useFetch
:- 管理数据、加载状态和错误状态。
- 使用
useEffect
发起数据请求,并在组件卸载时取消更新状态。 - 使用
useDebugValue
提供调试信息。
-
UserList
组件:- 使用
useState
管理请求的 URL。 - 调用
useFetch
获取用户数据。 - 使用
useCallback
记忆化refresh
函数,避免不必要的重新渲染。 - 根据加载状态和错误状态渲染不同的内容。
- 显示用户列表,并提供刷新按钮重新发起数据请求。
- 使用
总结
React Hooks 为函数组件带来了强大的功能,使得状态管理和副作用处理更加简洁和高效。本文详细介绍了 React 18.3 中的所有官方 Hooks,包括基础 Hooks、额外 Hooks 以及自定义 Hooks,并通过示例代码展示了它们的实际应用。掌握这些 Hooks 能够帮助开发者编写更清晰、可维护的 React 应用。
附录:完整代码示例
由于篇幅限制,本节仅提供上述示例的完整代码文件结构和内容概要。
javascript:title=src/hooks/useFetch.js
import { useState, useEffect, useDebugValue } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
setLoading(true);
fetch(url)
.then(response => response.json())
.then(json => {
if (isMounted) {
setData(json);
setLoading(false);
}
})
.catch(err => {
if (isMounted) {
setError(err);
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, [url]);
useDebugValue(data ? '数据加载完成' : '加载中');
return { data, loading, error };
}
export default useFetch;
javascript:title=src/view/UserList.js
import React, { useState, useCallback } from 'react';
import useFetch from '../hooks/useFetch';
function UserList() {
const [url, setUrl] = useState('https://jsonplaceholder.typicode.com/users');
const { data, loading, error } = useFetch(url);
const refresh = useCallback(() => {
setUrl('https://jsonplaceholder.typicode.com/users');
}, []);
if (loading) return <p>加载中...</p>;
if (error) return <p>发生错误:{error.message}</p>;
return (
<div>
<h1>用户列表</h1>
<button onClick={refresh}>刷新</button>
<ul>
{data.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
}
export default UserList;
javascript:title=src/view/Form.js
import React, { useState } from 'react';
// 定义自定义 Hook
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
const handleChange = (e) => {
setValue(e.target.value);
};
return {
value,
onChange: handleChange
};
}
function Form() {
const name = useFormInput('');
const email = useFormInput('');
const handleSubmit = (e) => {
e.preventDefault();
console.log('姓名:', name.value);
console.log('邮箱:', email.value);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>姓名:</label>
<input type="text" {...name} />
</div>
<div>
<label>邮箱:</label>
<input type="email" {...email} />
</div>
<button type="submit">提交</button>
</form>
);
}
export default Form;
javascript:title=src/view/Parent.js
import React, { useState, useCallback } from 'react';
import Child from './Child';
function Parent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<p>父组件计数:{count}</p>
<Child onIncrement={increment} />
</div>
);
}
export default Parent;
javascript:title=src/view/Child.js
import React from 'react';
function Child({ onIncrement }) {
console.log('Child 重新渲染');
return (
<button onClick={onIncrement}>子组件 +1</button>
);
}
export default React.memo(Child);
以上代码示例展示了如何在不同场景下使用 React Hooks,包括状态管理、性能优化、处理副作用等。通过这些示例,开发者可以更好地理解和掌握 React Hooks 的使用方法。