10 个内置 Hook:作用、用法、注意点、场景,逐个对标 vue3 语法
最近由 vue 转为 react,记录下 react 16.8 版本 hook 编程,当然 vue3 现在也流行 hook 编程,部分 hook 与 vue3 做对比使用。仅作记录,有帮助的可以取舍。
React 函数编程中内置的 Hook 是一套允许在函数组件中使用 state 和其他 React 特性的函数。React 提供了多个内置的 Hook,这些 Hook 极大地扩展了函数组件的功能,使得在无需编写类组件的情况下也能使用 React 的全部功能。即 hook 函数组件的,可以在函数内部使用类似于类组件的特性,下面一一介绍下。
npm create vite@latest react-test
选择 react 即可,按提示安装依赖并启动。
useState 是 React Hooks 中的一个基础 Hook,它允许你在函数组件中添加状态(state)。在类组件中,我们通过 this.state 来管理组件的状态,但在函数组件中,由于没有实例(this),所以 React 引入了 Hooks 来让函数组件也能拥有状态和其他 React 特性。
useState 的基本用法是调用它并传入一个初始状态值。它返回一个状态变量和一个让你更新它的函数,你可以在组件中通过这两个返回值来管理状态。
import { useState } from 'react'
import './App.css'
function App() {
// 它返回一个状态变量和一个更新该状态的函数
const [count, setCount] = useState(0)
// setCount 为更改状态 state 函数,每次调用会重新 render
return (
<>
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
</>
)
}
export default App
useState 的使用场景非常广泛,几乎涵盖了所有需要状态管理的场景。以下是一些常见的使用场景:
import { useState } from 'react'
import './App.css'
function App() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
console.log(`Username: ${username}, Password: ${password}`);
// 这里可以添加登录逻辑
};
return (
<form onSubmit={handleSubmit}>
<label>
Username:
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
</label>
<label>
Password:
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</label>
<button type="submit">Login</button>
</form>
);
}
export default App
useEffect 是 React Hooks 中的一个重要 Hook,它用于在函数组件中执行副作用操作。副作用是指那些发生在渲染之外,但可能与渲染相关的操作,如数据获取、订阅外部数据源、手动修改 DOM 等。useEffect 的主要作用是在组件渲染到屏幕之后执行这些副作用操作(副作用可以简单理解为,如类似 vue 中组件生命周期、监听 watch 等等)。
useEffect 接受两个参数:一个副作用函数和一个可选的依赖项数组。
import { useState, useEffect } from 'react'
import './App.css'
function App() {
useEffect(() => {
// 副作用操作
console.log('Component mounted or updated');
// 清理函数(可选)
return () => {
console.log('Cleanup function called');
};
}, [/* 依赖项数组 */]);
return <div>My Component</div>;
}
export default App
useEffect 可以通过不同的方式来模拟类组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期方法。
当你想要在某个组件挂载到 DOM 后执行代码,并在组件卸载时执行清理操作(如取消网络请求、移除事件监听器等)时,可以将一个不带依赖项数组(或空数组)的 useEffect 用作 componentDidMount,并通过返回一个清理函数来模拟 componentWillUnmount。
import { useEffect } from 'react'
import './App.css'
function App() {
useEffect(() => {
// 相当于 componentDidMount
console.log('Component mounted');
// 假设我们在这里设置了一个定时器
const timerId = setTimeout(() => {
console.log('Timer executed');
}, 1000);
// 清理函数,相当于 componentWillUnmount
return () => {
clearTimeout(timerId);
console.log('Component will unmount');
};
}, []); // 空数组表示这些副作用仅在挂载和卸载时运行
return <div>Hello, MyComponent!</div>;
}
export default App
如果你想要在某个状态或属性变化时执行副作用,可以将这些状态或属性作为 useEffect 的依赖项数组的一部分。这样,每当依赖项变化时,副作用就会重新执行。
import { useState, useEffect } from 'react'
import './App.css'
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
// 每当 count 变化时,这里的代码就会执行
// 相当于 componentDidUpdate(但请注意,它也会在首次渲染后执行)
console.log(`Count is: ${count}`);
// 这里不需要返回清理函数,除非有资源需要清理
}, [count]); // 依赖项数组包含 count
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App
注意:useEffect 也会在组件首次渲染后执行。如果你想要避免在首次渲染时执行副作用,可以通过添加一个额外的条件来检查(例如,检查某个状态是否已初始化)。
import { useState, useEffect } from 'react'
import './App.css'
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
const response = await fetch('https://api.example.com/user');
const data = await response.json();
setUser(data);
};
// 初始化执行请求
fetchUser();
// 清理函数(在这个例子中可能不需要,因为没有需要清理的资源)
return () => {};
}, []); // 空数组表示只在组件挂载时执行一次
if (!user) {
return <div>Loading...</div>;
}
return <div>Name: {user.name}</div>;
}
export default App
useRef 是 React Hooks 中的一个重要 Hook,它提供了一种在组件的整个生命周期内保持数据不变的方式。与 useState 不同,useRef 返回的引用对象在组件的整个生命周期内保持不变,包括其 .current 属性。这使得 useRef 非常适用于那些不需要引起组件重新渲染,但需要跨渲染周期保持数据的场景。可以简单理解为 vue 中的 ref,获取 DOM 的引用。
useRef 接收一个初始值作为参数(这个初始值只在组件的第一次渲染时设置),并返回一个对象,该对象拥有一个 .current 属性,你可以通过这个属性来访问或修改存储的值。
import { useRef } from 'react'
import './App.css'
function App() {
const myRef = useRef(null); // initialValue 是可选的
// 访问或修改 ref 的值
console.log(myRef.current); // 访问
myRef.current = "aaa"; // 修改
console.log(myRef.current); // 访问
return <div>My Component</div>;
}
export default App
import { useRef } from 'react'
import './App.css'
function App() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
export default App
import { useRef, useEffect } from 'react'
import './App.css'
function App() {
const timerRef = useRef(null);
useEffect(() => {
timerRef.current = setTimeout(() => {
console.log('Timer executed');
}, 1000);
return () => {
clearTimeout(timerRef.current);
};
}, []);
return <div>Timer will execute after 1 second</div>;
}
export default App
import { useRef, useEffect, useState } from 'react'
import './App.css'
function App() {
const prevCountRef = useRef(0);
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count changed from ${prevCountRef.current} to ${count}`);
prevCountRef.current = count;
}, [count]);
return (
<div>
<p>prevCountRef: {prevCountRef.current}</p>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App
在这个例子中,prevCountRef 用于存储上一次的 count 值,以便在 count 变化时进行比较。prevCountRef.current 自点击后,总比 count 小 1,即存储的是上一次的 count 状态。
useMemo 是 React 提供的一个 Hook,主要用于性能优化。它通过缓存函数的计算结果来避免在每次组件渲染时都重新执行昂贵的计算或重新创建相同的对象。只有当依赖项发生变化时,useMemo 才会重新计算并返回新的结果,否则它会返回上一次缓存的结果。类似于 vue 的 computed。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
import { useState, useMemo } from 'react'
import './App.css'
const ExpensiveComponent = ({ input }) => {
const expensiveValue = useMemo(() => {
// 假设这是一个非常昂贵的计算过程
return computeExpensiveValue(input);
}, [input]);
return <div>{expensiveValue}</div>;
};
// 假设 computeExpensiveValue 是一个实际存在的昂贵计算函数
function computeExpensiveValue(input) {
// ... 执行一些昂贵的计算 ...
return `Processed ${input}`;
}
function App() {
const [input, setInput] = useState('initial value');
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter something"
/>
<ExpensiveComponent input={input} />
</div>
);
}
export default App
// 使用 useMemo 计算属性
const chunklistChecked = useMemo(() => {
return chunklist
.filter((item) => item.isChecked)
}, [chunklist]);
当 chunklist 变化时,筛选出 chunklist 中 item.isChecked 的项。类似于计算属性。
useCallback 是 React 中的一个 Hook,它返回一个记忆化的回调函数。这个回调函数只有在它的依赖项改变时才会更新。useCallback 的主要作用是防止在每次渲染时都创建新的函数实例,这对于优化性能以及避免在子组件中由于父组件频繁重新渲染而导致的不必要的重新渲染是非常有用的。
const memoizedCallback = useCallback(
() => {
// 执行一些操作
},
[dep1, dep2, /* ... */],
);
当你将回调函数作为 prop 传递给子组件,并且子组件依赖于该回调函数的引用稳定性时,可以使用 useCallback 来避免在父组件每次渲染时都创建新的函数实例。
import { useState, useEffect, useCallback } from 'react'
import './App.css'
const Child = ({ onChildEvent }) => {
useEffect(() => {
// 假设这里有一些逻辑,当某些事件发生时调用 onChildEvent
console.log('Child effect runs');
}, [onChildEvent]); // 注意这里的依赖项
return <div>Child Component</div>;
};
function App() {
const [count, setCount] = useState(0);
// 使用 useCallback 来记忆化回调函数
const memoizedCallback = useCallback(() => {
console.log('Callback called from child');
}, []); // 这里的依赖项数组为空,因为回调函数不依赖于任何状态或 prop
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
{Array.from({ length: count }, (_, i) => (
<Child key={i} onChildEvent={memoizedCallback} />
))}
</div>
);
}
export default App
useImperativeHandle 是 React 中的一个自定义 Hook,它的主要作用是允许子组件自定义暴露给父组件的实例值。这通常与 forwardRef 一起使用,以便父组件可以通过 ref 访问子组件内部的方法或属性。通过使用 useImperativeHandle,子组件可以选择性地暴露其内部状态或方法给父组件,从而实现更灵活和可控的组件交互。
useImperativeHandle(ref, createHandle, [deps]);
向父组件暴露子组件的方法:当父组件需要调用子组件内部的方法时,可以使用 useImperativeHandle 将这些方法暴露给父组件。
import { useRef, useImperativeHandle, forwardRef } from 'react'
import './App.css'
// 子组件
const ChildComponent = forwardRef((props, ref) => {
const doSomething = () => {
console.log('Doing something in ChildComponent');
};
useImperativeHandle(ref, () => ({
doSomething,
}));
return <div>Child Component</div>;
});
function App() {
const childRef = useRef(null);
const handleClick = () => {
if (childRef.current) {
childRef.current.doSomething();
}
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleClick}>Call Child Method</button>
</div>
);
}
export default App
useContext 是 React 提供的一个钩子(Hook),用于在函数式组件中访问上下文(Context)的值。它使得在组件树中传递数据变得更加简单和高效,特别是当需要在多个层级之间共享数据时,避免了通过显式的 props 层层传递的繁琐。通过 useContext,组件可以直接访问到由 Context.Provider 提供的值,实现了跨组件层级的数据共享。
import { useState, createContext, useContext } from 'react'
import './App.css'
// 创建一个 Context
const ThemeContext = createContext({ theme: 'light' });
// 父组件,设置主题
function ParentComponent() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme }}>
<ChildComponent />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button>
</ThemeContext.Provider>
);
}
// 子组件,使用主题
function ChildComponent() {
const { theme } = useContext(ThemeContext);
return <div style={{ color: theme === 'light' ? 'black' : 'red' }}>Hello, {theme} theme!</div>;
}
// 渲染应用
function App() {
return <ParentComponent />;
}
export default App
useReducer 是 React 提供的一个用于状态管理的 Hook,它接收一个 reducer 函数和初始状态作为参数,并返回当前状态以及一个 dispatch 函数。useReducer 的主要作用是处理组件中复杂的状态逻辑,特别是当状态更新依赖于先前的状态或需要执行一些复杂计算时。
import { useReducer } from 'react'
import './App.css'
// 定义初始状态
const initialState = { count: 0 };
// 定义 reducer 函数
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 App() {
// 使用 useReducer 初始化状态和获取 dispatch 函数
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
export default App
| 对比项 | useState | useReducer |
|---|---|---|
| 语法 | const [state, setState] = useState(initialState) | const [state, dispatch] = useReducer(reducer, initialState) |
| 适用场景 | 简单的状态管理(表单输入、开关等) | 复杂的状态逻辑(购物车、多操作计数器等) |
| 复杂度 | 适合单个或简单状态集合 | 适合多个相关状态,按 action.type 拆分更新逻辑 |
| 更新逻辑 | 独立更新,不受之前状态影响 | reducer 按动作类型定义不同更新逻辑 |
useLayoutEffect 是 React 中的一个 Hook,用于在所有的 DOM 变更之后同步执行某些操作。这些操作会在浏览器进行绘制之前完成,因此可以用于在渲染输出到屏幕之前读取 DOM 布局并同步触发重绘或进行其他需要同步执行的 DOM 操作。
useLayoutEffect 的用法与 useEffect 非常相似,它们都接受一个包含副作用操作的函数和一个依赖项数组作为参数。但是,useLayoutEffect 中的副作用操作会在浏览器绘制之前同步执行。
import { useState, useLayoutEffect } from 'react'
import './App.css'
function App() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
// 这里的代码会在 DOM 更新后立即同步执行
// 如果你在这里读取 DOM 的某些属性或执行影响布局的操作
// 它会在浏览器绘制之前完成
const myElement = document.getElementById('my-element');
if (myElement) {
myElement.style.color = 'red'; // 这将在浏览器绘制之前设置颜色
}
return () => {
// 清理函数会在组件卸载前或下一次 useLayoutEffect 运行前执行
};
}, [count]); // 依赖项数组
return (
<div>
<p id="my-element">Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App
import { useRef, useLayoutEffect } from 'react'
import './App.css'
function App() {
const inputRef = useRef(null);
useLayoutEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []); // 空数组表示只在组件挂载时执行
return <input ref={inputRef} type="text" placeholder="Type here..." />;
}
export default App
| 对比项 | useEffect | useLayoutEffect |
|---|---|---|
| 触发时机 | 组件渲染到屏幕之后异步执行,不阻塞绘制 | DOM 更新完成后同步执行,但在浏览器绘制之前 |
| 对渲染影响 | 不阻塞页面渲染,适合数据获取、订阅、定时器 | 可能阻塞渲染,适合读取布局、调整滚动、设置焦点 |
| 使用场景 | 大多数不需要立即执行的副作用 | 需要 DOM 更新后立即同步执行的副作用,避免闪烁 |
React 的 useDebugValue 是一个 Hook,它主要用于在开发过程中帮助开发者调试自定义 Hook。它的主要作用是将自定义 Hook 中的某些值暴露给 React 开发工具(如 React DevTools),以便于开发者在调试时能够更直观地查看和理解组件的状态。它类似于在开发过程中为自定义 Hook 打上了一个"标签"。
import { useState, useDebugValue } from 'react'
import './App.css'
function useMyCounter(initialCount = 0) {
const [count, setCount] = useState(initialCount);
// 使用 useDebugValue 暴露 count 值给 React DevTools
useDebugValue(`Count: ${count}`);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return [count, increment, decrement];
}
// 渲染应用
function App() {
const [count, increment, decrement] = useMyCounter(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default App
以上信息部分借助 AI 工具生成,全部例子经过验证,仅作记录,按需取舍。