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:在布局和绘制完成之前同步执行副作用。
