Hooks
Hooks
React Hooks 的诞生
在 React 16.8 之前,函数组件是无状态的,所有状态管理只能在类组件中完成。这意味着,如果你想要在函数组件中管理状态、使用生命周期方法或者其他特性,你必须转换为类组件。
React 团队意识到这一点后,决定给我们带来一个全新的工具——Hooks。通过 Hooks,函数组件也能拥有状态和生命周期方法,彻底摆脱类组件的束缚。于是,Hooks 出场了,成为了开发者们的新宠。
useState 管理状态
useState
是最常用的 Hook,用于在函数组件中管理状态。它允许我们在函数组件中定义内部状态,并提供了一个更新状态的函数。
基本用法
useState
接受一个初始值作为参数,返回一个数组,其中第一个元素是状态值,第二个元素是更新状态的函数。
更新单个状态
import React, { useState } from 'react';
function Examples() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Examples;
这个例子展示了一个简单的计数器。每次点击按钮时,count 状态都会增加 1,组件会重新渲染以显示最新的计数。useState
返回了一个状态值和一个更新函数,这样我们就可以随时更新状态了。
更新对象
import React, { useState } from 'react';
function Examples() {
const [user, setUser] = useState({ name: 'Alice', age: 25 });
// 方法一:直接更新user对象中name属性
function updateUserName(newName) {
// 第一种,注意这里的user是上一次的user对象,所以要用prevUser
setUser(prevUser => ({ ...prevUser, name: newName }));
// 第二种,直接更新user对象
// setUser({ ...user, name: newName });
}
// 方法2:直接整个user对象中更新年龄
function updateUserAge() {
// 第一种,注意这里的user是上一次的user对象,所以要用prevUser
setUser(prevUser => ({ ...prevUser, age: 30 }));
// 第二种,直接更新user对象
// setUser({ ...user, age: 30 });
}
// 方法3:传入新的user对象更新
function updateUserObj(newUser) {
setUser(newUser);
}
return (
<div>
<p>姓名: {user.name}</p>
<p>年龄: {user.age}</p>
<button onClick={() => updateUserName('Bob')}>更新姓名</button>
<button onClick={updateUserAge}>更新年龄</button>
<button onClick={()=>updateUserObj({ name: 'Tom', age: 35 })}>更新对象</button>
</div>
);
}
export default Examples;
这个案例,展示了如何更新对象。我们可以直接更新对象属性,也可以传入一个新的对象来更新。
多个状态
import React, { useState } from 'react';
function Examples() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Alice');
return (
<div>
<p>Count: {count}</p>
<p>Name: {name}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setName('Bob')}>Change Name</button>
</div>
);
}
export default Examples;
这个例子展示了如何管理多个状态。我们可以同时管理多个状态,只需要调用 useState
多次即可。
常见useState更新问题
疑问
提示
为什么ceshi只加了1,明明设置了三次加1,ceshi显示的是1,而不是3。之前对于这里只是知道一点异步之类的。
export default memo(function TalentBox() {
const [ceshi,setceshi] = useState(0)
return (
<TalentBoxWrapper>
{ceshi} // 显示 1,而不是一次性加了3
<button onClick={()=>{
setceshi(ceshi+1)
setceshi(ceshi+1)
setceshi(ceshi+1)
}}>132</button>
<div className="dss-talent-box-left">1</div>
<div className="dss-talent-box-right">2</div>
</TalentBoxWrapper>
)
})
在React中,useState
的状态更新是异步的,并且在同一个事件处理函数中多次调用状态更新函数(如setceshi
)时,React会将这些更新合并为一次更新,这意味着在同一个时间循环中多次调用setceshi
,只有最后一次调用的结果会生效。因此,在上述代码中,虽然调用了三次setceshi(ceshi + 1)
,但实际上它们会被合并为一次更新,只有最后一次setceshi(ceshi + 1)
的调用结果会生效。(注意⚠️:只有最后一次的调用结果生效了,并不是说只有最后一次的setceshi(ceshi + 1)
执行了,setceshi(ceshi + 1)
每次都执行了。)
要解决这个问题并确保每次点击按钮时ceshi
增加3,可以使用函数式的状态更新方法。这样可以确保每次更新都基于最新的状态值。修改后的代码如下:
const [ceshi, setceshi] = useState(0);
return (
<TalentBoxWrapper>
{ceshi}
<button onClick={() => {
setceshi(prevCeshi => prevCeshi + 1);
setceshi(prevCeshi => prevCeshi + 1);
setceshi(prevCeshi => prevCeshi + 1);
}}>132</button>
</TalentBoxWrapper>
);
在这个例子中,prevCeshi
是前一个状态值,每次调用setceshi
时都会基于最新的状态值进行更新,因此每次点击按钮时ceshi会增加3
原因
在React中,使用函数式的状态更新可以确保每次更新都基于最新的状态值。这是因为函数式更新接收一个函数作为参数,这个函数的参数是当前的状态值,而不是闭包中的旧状态值。
闭包问题
在你的原始代码中:
<button onClick={() => {
setceshi(ceshi + 1);
setceshi(ceshi + 1);
setceshi(ceshi + 1);
}}>132</button>
每次调用setceshi(ceshi + 1)
时,ceshi的值都是事件处理函数开始执行时的值(即闭包中的值)。由于React的批处理机制,这些更新会被合并,最终只会增加一次。
函数式更新
使用函数式更新可以避免这个问题:
<button onClick={() => {
setceshi(prevCeshi => prevCeshi + 1);
setceshi(prevCeshi => prevCeshi + 1);
setceshi(prevCeshi => prevCeshi + 1);
}}>132</button>
在这个例子中,每次调用setceshi
时,传递给更新函数的prevCeshi
都是最新的状态值。具体过程如下:
- 第一次调用
setceshi(prevCeshi => prevCeshi + 1)
时,prevCeshi
是当前的状态值,比如0。 - React会将这个更新放入队列,并在适当的时候处理它。
- 第二次调用
setceshi(prevCeshi => prevCeshi + 1)
时,prevCeshi
是第一次更新后的状态值,比如1。 - 第三次调用
setceshi(prevCeshi => prevCeshi + 1)
时,prevCeshi
是第二次更新后的状态值,比如2。
这样,每次更新都是基于最新的状态值,因此最终的状态值会正确地累加3。
useEffect 管理副作用
useEffect
用于在函数组件中处理副作用,比如数据获取、订阅、手动更改 DOM 等,它相当于类组件中的生命周期函数
。
useEffect
可以让你在函数组件中执行副作用操作,接收两个参数,第一个参数是要执行的函数 callback
,第二个参数是可选的依赖项数组 dependencies
。其中依赖项是可选的,如果不指定,那么 callback
就会在每次函数组件执行完后都执行;如果指定了,那么只有依赖项中的值发生变化的时候,它才会执行。 简单来说就是当我们的依赖项发生发生变化的时候,可以异步的执行里面的回调。
提示
注意:useEffect是在render之后执行
使用useEffect监听state的变化
import React, { useState, useEffect } from 'react';
export default function hook() {
const [num, setNum] = useState(1)
/**
* 第一个参数是回调函数
* 第二个参数是依赖项
* 每次num变化时都会变化
*
* 注意初始化的时候,也会调用一次
*/
useEffect(() => {
console.log("每次num,改变我才会触发")
}, [num])
// useEffect(() => {
// console.log("每次num,改变我才会触发")
// }, [num,setNum]) //这样在两个值变化的时候,也会触发
return (
<div>
<button onClick={() => setNum(num + 1)}>+1</button>
<div>你好,react hook{num}</div>
</div>
);
}
useEffect第二个参数不传
import React, { useState, useEffect } from 'react';
export default function hook() {
const [num, setNum] = useState(1)
useEffect(() => {
console.log("每次render页面我就会触发")
})
return (
<div>
<button onClick={() => setNum(num + 1)}>+1</button>
<div>你好,react hook{num}</div>
</div>
);
}
小细节
可以没有返回值,要是有返回值必须是一个函数!!假如你想在里面用async await?

只能这样写

监听卸载
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
// 使用 useEffect 来实现一个计时器,每秒增加一次计数
useEffect(() => {
const timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// 清除副作用,类似于 componentWillUnmount
return () => clearInterval(timer);
}, []); // 空数组表示只在组件挂载时执行一次
return <div>Count: {count}</div>;
}
export default Timer;
在这里,useEffect
只会在组件初次渲染时启动计时器,并在组件卸载时清除计时器,避免内存泄漏。
useContext 共享状态
useContext
用于从 React 的上下文中获取共享数据,可以用做跨组件传参
如果你有多个组件需要访问同样的状态或数据,useContext
会是你最好的朋友。它能够让你轻松在组件树中传递数据,而无需通过每层组件都手动传递 props
。
主要API
React.createContext
:
- 创建一个 Context 对象,可以传递一个默认值。
- 基本语法:
const MyContext = React.createContext(defaultValue);
MyContext.Provider
:
- 使用 Provider 来包裹需要共享数据的组件,并通过 value 属性传递数据。
- 基本语法:
<MyContext.Provider value={sharedData}>
<MyComponent />
</MyContext.Provider>
MyContext.Consumer
:
- 使用 Consumer 来包裹需要共享数据的组件,通过函数作为子组件的方式访问 Context 值。
- 基本语法:
<MyContext.Consumer>
{value => (
// 根据 context 值渲染的 JSX
)}
</MyContext.Consumer>
useContext
:
- 使用 useContext hook 来获取 Context 值。
- 基本语法:
const value = useContext(MyContext);
基本用法
import React, { useContext, createContext } from "react";
const ThemeContext = createContext("light");
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme === "dark" ? "#333" : "#FFF" }}>
Click me
</button>
);
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}
在这个例子中,useContext
让我们可以直接在 ThemedButton
组件中访问 ThemeContext
的值,按钮的背景色根据上下文主题动态变化。而无需通过 props
逐层传递。这样,开发者可以在更复杂的应用中更轻松地管理全局状态。
基本使用
创建 Context
使用 React.createContext
创建一个 Context
对象。可以传递一个默认值,这个默认值会在没有 Provider
包裹时使用。高亮的这句话会在介绍完Api后详细介绍。
基本语法:
const MyContext = React.createContext(defaultValue);
React.createContext类似于一个store
,类似于vuex,pinia
创建了一个第三方中间状态管理仓库,一般都是定义在一个单独的js文件中。代码示例如下:
//useThemeContext.ts
import React from 'react'
const useThemeContext = React.createContext('default')
export default useThemeContext
提供 Context
使用 MyContext.Provider
来包裹需要共享数据的组件,并通过 value
属性传递数据。
基本语法:
<MyContext.Provider value={sharedData}>
<MyComponent />
</MyContext.Provider>
代码示例如下:
//Root.tsx
import { FC, useState } from 'react'
import useThemeContext from './useThemeContext' // 创建好的 Context 对象
// components -子组件
import CardItem from './CardItem.tsx'
const Root: FC = () => {
return (
<useThemeContext.Provider value={{ name: 'Person', age: 20 }}> //使用 MyContext.Provider 传递值
<div>我是父组件</div>
我是子组件
<CardItem></CardItem>
</useThemeContext.Provider>
)
}
export default Root
消费/使用Context
使用context
方式有两种,一种是MyContext.Consumer
,另一种就是使用useContext
的hooks
,下面我们都会介绍。
第一种: 使用 MyContext.Consumer
来包裹需要共享数据的组件,并通过 value 属性传递数据。
<MyContext.Consumer>
{value => (
// 根据 context 值渲染的 JSX
)}
</MyContext.Consumer>
码示例如下:
//CardItem.tsx
import { Card, Space } from 'antd'
import useThemeContext from './useThemeContext' // 创建好的 Context 对象
const CardItem = () => {
return (
<useThemeContext.Consumer> //使用MyContext.Consumer 获得context
{(ctx: any) => (
<>
<Card>
<p>{ctx.name}</p>
</Card>
</>
)}
</useThemeContext.Consumer>
)
}
export default CardItem
第二种: 使用useContext获取context
基本语法:
const value = useContext(MyContext);
代码示例如下:
//CardItem.tsx
import { Card, Space } from 'antd'
import useThemeContext from './useThemeContext' // 假设 useThemeContext 导出了 ThemeContext
import { useContext } from 'react'
const CardItem = () => {
const ctx = useContext(useThemeContext) //使用hooks获取
return (
<>
<Card>
<p>{ctx.name}</p>
</Card>
</>
)
}
export default CardItem
提示
使用React.createContext
创建一个 Context
对象。可以传递一个默认值,这个默认值会在没有 Provider
包裹时使用。
当我们没有使用MyContext.Provider
方式提供数据的时候时候,我们使用useContext
获取到的值就是createContext(defaultValue)
的defaultValue
。
useReducer 管理复杂状态
如果你觉得 useState
太简单,无法满足复杂状态逻辑的需求,那么 useReducer
会是一个更合适的选择。它的工作方式类似于 Redux
,允许你通过分发 action
来更新状态。
useReducer
是一个很有用的 Hook,它可以让你把组件的状态逻辑和 UI 逻辑分离,并通过一个函数来描述状态更新逻辑。
useReducer
接收两个参数,第一个参数是一个 reducer 函数,第二个参数是初始状态。
const [state, dispatch] = useReducer(reducer, initialArg, init);
reducer
函数是一个纯函数,接收两个参数,第一个参数是当前的状态,第二个参数是要 dispatch 的 action。initialArg
是可选的,指定了初始状态。init
是可选的,是一个函数,用来初始化 reducer。
基本用法
import React, { useReducer } from "react";
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, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}
export default Counter;
这个例子展示了一个更复杂的计数器,通过 useReducer
和 dispatch
更新状态。useReducer
特别适合于状态逻辑复杂或者需要分组管理状态的场景。
自定义初始状态
import React, { useReducer } from "react";
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, { count: 10 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}
export default Counter;
自定义初始化函数
import React, { useReducer } from "react";
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 init() {
return { count: 10 };
}
function Counter() {
const [state, dispatch] = useReducer(reducer, init, init);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}
export default Counter;
多个状态管理
import React, { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "increment":
return {...state, count: state.count + 1 };
case "decrement":
return {...state, count: state.count - 1 };
case "changeName":
return {...state, name: action.payload };
default:
throw new Error();
}
}
function init() {
return { count: 0, name: "Person" };
}
function Counter() {
const [state, dispatch] = useReducer(reducer, init, init);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<br />
Name: {state.name}
<br />
<input
type="text"
value={state.name}
onChange={(e) =>
dispatch({ type: "changeName", payload: e.target.value })
}
/>
</div>
);
}
export default Counter;
注意事项
useReducer
是一个很有用的 Hook,它可以让你把组件的状态逻辑和 UI 逻辑分离,并通过一个函数来描述状态更新逻辑。useReducer
接收两个参数,第一个参数是一个 reducer 函数,第二个参数是初始状态。useReducer
返回一个数组,数组的第一个元素是当前的状态,第二个元素是 dispatch 函数。useReducer
不会自动触发组件的重新渲染,需要手动调用dispatch
函数来触发重新渲染。useReducer
适用于管理复杂的状态,但不要滥用它,因为它会增加组件的复杂度。
useRef 获取元素
useRef
提供了一种直接访问 DOM 元素的方式,并且它还可以用于存储不会导致组件重新渲染的可变值。 useRef
是一个可变的 ref 对象,它可以保存一个可变的 value。 useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数 initialValue
。
const refContainer = useRef(initialValue);
initialValue
是可选的,指定了.current
属性的初始值。- 返回的 ref 对象在组件的整个生命周期内保持不变。
useRef
主要用于获取 DOM 节点或其他可变值。
基本用法
import React, { useRef } from "react";
function TextInput() {
const inputEl = useRef(null);
const handleClick = () => {
inputEl.current.focus();
};
return (
<div>
<input type="text" ref={inputEl} />
<button onClick={handleClick}>Focus</button>
</div>
);
}
export default TextInput;
这个例子展示了一个 TextInput
组件,它有一个 input
元素,点击按钮可以让 input
元素获得焦点。
挂载时输入框自动获取焦点
import React, { useRef, useEffect } from 'react';
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// 在组件挂载时让输入框自动获取焦点
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
export default FocusInput;
在这个示例中,useRef
引用一个输入框,useEffect
确保组件挂载时输入框获得焦点。
注意事项
useRef
是一个可变的 ref 对象,它可以保存一个可变的 value。useRef
返回一个可变的 ref 对象,其.current
属性被初始化为传入的参数initialValue
。useRef
主要用于获取 DOM 节点或其他可变值。useRef
不会触发组件的重新渲染。useRef
适用于需要保存一个可变值,但又不想触发组件重新渲染的场景。
useMemo 缓存计算结果
useMemo
是一个可以缓存计算结果的 Hook,它接受一个函数和一个依赖数组作为参数,并返回该函数的执行结果。
const memoizedValue = useMemo(createExpensiveValue, [a, b]);
createExpensiveValue
是函数,会在组件渲染时执行。a
和b
是依赖数组,只有当a
或b
变化时,才会重新计算函数的结果。useMemo
返回一个 memoizedValue,它是createExpensiveValue
的执行结果。
基本用法
import React, { useMemo } from "react";
function ExpensiveComponent() {
const expensiveValue = useMemo(() => {
console.log("计算昂贵的值");
return Math.random();
}, []);
return <div>{expensiveValue}</div>;
}
export default ExpensiveComponent;
这个例子展示了一个 ExpensiveComponent
,它会在每次渲染时打印一条日志,并返回一个随机数。
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation(num) {
console.log('Expensive calculation running...');
return num * num;
}
function App() {
const [count, setCount] = useState(0);
const [input, setInput] = useState('');
// 使用 useMemo 缓存计算结果,只有 count 改变时才会重新计算
const squared = useMemo(() => ExpensiveCalculation(count), [count]);
return (
<div>
<p>Count: {count}</p>
<p>Squared: {squared}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<input value={input} onChange={e => setInput(e.target.value)} placeholder="Type something..." />
</div>
);
}
export default App;
useMemo
在这个例子中确保只有 count
变化时才会重新计算平方值,从而避免不必要的计算。
注意事项
useMemo
是一个可以缓存计算结果的 Hook,它接受一个函数和一个依赖数组作为参数,并返回该函数的执行结果。useMemo
缓存了函数的执行结果,只有当依赖数组中的依赖变化时,才会重新计算函数的结果。useMemo
不会触发组件的重新渲染。useMemo
适用于需要缓存计算结果,但又不想触发组件重新渲染的场景。
useCallback 缓存函数
useCallback
是一个可以缓存函数的 Hook,它接受一个函数和一个依赖数组作为参数,并返回该函数的引用。
const memoizedCallback = useCallback(createFunction, [a, b]);
createFunction
是函数,会在组件渲染时执行。a
和b
是依赖数组,只有当a
或b
变化时,才会重新计算函数的引用。useCallback
返回一个 memoizedCallback,它是createFunction
的引用。
基本用法
import React, { useCallback } from "react";
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<Child onClick={handleClick} />
</div>
);
}
function Child({ onClick }) {
return <button onClick={onClick}>Increment</button>;
}
export default Parent;
这个例子展示了一个 Parent
组件,它有一个 Child
组件,Child
组件的 onClick
事件绑定了 Parent
组件的 handleClick
函数。
import React, { useState, useCallback } from "react";
function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
const handleInputChange = useCallback((e) => {
setText(e.target.value);
}, []);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Text: {text}</p>
<input type="text" value={text} onChange={handleInputChange} />
<button onClick={handleClick}>Increment</button>
</div>
);
}
export default App;
useCallback
在这个例子中确保了 handleInputChange
函数的引用不会因为 text
的变化而变化,从而避免不必要的渲染。
注意事项
useCallback
是一个可以缓存函数的 Hook,它接受一个函数和一个依赖数组作为参数,并返回该函数的引用。useCallback
缓存了函数的引用,只有当依赖数组中的依赖变化时,才会重新计算函数的引用。useCallback
不会触发组件的重新渲染。useCallback
适用于需要缓存函数引用,但又不想触发组件重新渲染的场景。
useLayoutEffect 同步布局和动画
useLayoutEffect
的作用与 useEffect
类似,都是用于执行副作用,但它的触发时机有所不同。 useLayoutEffect
会在浏览器完成 DOM 的布局和渲染之后,同步地运行回调函数。这使得它更适合需要读取 DOM 布局信息并进行同步更新的场景,比如测量 DOM 大小或强制重绘。
如何使用
useLayoutEffect
的用法和 useEffect
类似,它也接收一个回调函数和依赖项数组。不同的是,useLayoutEffect
在 DOM 更新后但在浏览器完成绘制之前同步运行。
useLayoutEffect(createEffect, [a, b]);
createEffect
是函数,会在组件渲染之后执行。a
和b
是依赖数组,只有当a
或b
变化时,才会重新执行函数。useLayoutEffect
不会阻塞浏览器更新,它会在浏览器更新之后执行。
基本用法
import React, { useLayoutEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App;
这个例子展示了一个 App
组件,它会在每次点击按钮时更新浏览器标题。
import React, { useLayoutEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => {
clearInterval(intervalId);
};
}, [count]);
return (
<div>
<p>Count: {count}</p>
</div>
);
}
export default App;
useLayoutEffect
在这个例子中使用 setInterval
定时器,每隔一秒更新 count
状态。
import React, { useLayoutEffect, useRef } from 'react';
function LayoutComponent() {
const divRef = useRef(null);
useLayoutEffect(() => {
// 在 DOM 更新后立即读取并修改布局
console.log('Div width:', divRef.current.offsetWidth);
});
return <div ref={divRef} style={{ width: '100%' }}>Resize me!</div>;
}
export default LayoutComponent;
在这个示例中,useLayoutEffect
会在 DOM 元素更新后立即读取宽度信息,而不是等到屏幕完成绘制。
注意事项
useLayoutEffect
是一个特殊的 Hook,它会在组件渲染之后同步执行,并且会在 DOM 更新之后同步执行。useLayoutEffect
不会阻塞浏览器更新,它会在浏览器更新之后执行。useLayoutEffect
适用于需要同步执行 DOM 操作和动画的场景。
useImperativeHandle+forwardRef
useImperativeHandle
是一个可以自定义暴露给父组件的实例的 Hook,它可以让父组件能够访问子组件的实例。
forwardRef
: 它允许父组件能够通过 ref 直接访问函数组件内部的某些 DOM 元素或方法。通常情况下,ref 无法直接作用于函数组件,而通过forwardRef
,我们可以将 ref 从父组件传递到子组件,并应用到子组件的内部 DOM 元素上。useImperativeHandle
: 它允许子组件有选择性地暴露一些内部方法或属性给父组件。通常情况下,父组件使用 ref 只能访问子组件的 DOM 元素,而通过useImperativeHandle
,我们可以让父组件不仅仅访问 DOM 元素,还能访问子组件内部定义的逻辑,比如某个自定义方法。
useImperativeHandle(ref, createHandle, [deps])
ref
是父组件的 ref 对象。createHandle
是函数,会在组件渲染时执行。deps
是依赖数组,只有当deps
变化时,才会重新执行createHandle
函数。useImperativeHandle
可以让父组件访问子组件的实例。
基本用法
import React, { forwardRef, useImperativeHandle } from "react";
function FancyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input type="text" ref={inputRef} />;
}
const FancyInputWrapper = forwardRef(FancyInput);
export default FancyInputWrapper;
这个例子展示了一个 FancyInput
组件,它有一个 input
元素,并且暴露了一个 focus
方法给父组件。
import React, { useRef } from "react";
function Parent() {
const inputRef = useRef(null);
return (
<div>
<FancyInputWrapper ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
);
}
function FancyInput(props) {
const inputRef = useRef(null);
useImperativeHandle(props.ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input type="text" ref={inputRef} />;
}
const FancyInputWrapper = forwardRef(FancyInput);
export default Parent;
这个例子展示了一个 Parent
组件,它有一个 FancyInputWrapper
组件,并且通过 ref
属性将 FancyInput
组件的实例暴露给父组件。
注意事项
useImperativeHandle
是一个可以自定义暴露给父组件的实例的 Hook,它可以让父组件能够访问子组件的实例。useImperativeHandle
接收三个参数,第一个参数是父组件的 ref 对象,第二个参数是一个函数,会在组件渲染时执行,第三个参数是依赖数组。useImperativeHandle
可以让父组件访问子组件的实例。
总结
总结
- useState:管理组件状态。
- useEffect:处理副作用(如数据获取)。
- useContext:在组件树中共享数据。
- useReducer:处理复杂状态逻辑。
- useRef:访问 DOM 或存储不需要触发渲染的变量。
- useMemo:缓存计算结果,优化性能。
- useCallback:缓存函数引用,避免不必要的重新创建。
- useImperativeHandle:让父组件访问子组件的方法。
- useLayoutEffect:在布局和绘制完成之前同步执行副作用。