学习如何轻松构建可伸缩的 React 应用程序:Ract Hooks
React Hooks 是在函数式组件中使用的生命周期方法,React Hooks 在 React 16.8 中被引入。在类组件中的生命周期方法已被合并成 React Hooks,React Hooks 无法在类组件中使用。
其中一些内置的 React Hooks 包括以下几个:
useState
useReducer
useEffect
useLayoutEffect
useMemo
useCallback
useRef
useContext
在使用 React Hooks 时,需要遵循一些规则:
useState
方法是常用的 React Hooks 之一。该 Hook 被归类为 React 中的受控组件中,useState
方法设置了一个初始值,可以随着用户执行操作而更新。
import React, { useState } from "react";
function Example() {
// 声明一个新的叫做 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
useReducer 方法是常用的 React Hooks 之一。当应用程序中存在复杂的状态更改时,可以使用此 Hook,类似于 useState
,但是需要发送 action
来更新状态:
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();
}
}
const initialState = { count: 0 };
function Example() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
}
export default Example;
useEffect
方法是常用的 React Hooks 之一。useEffect
有两个参数(箭头函数和可选的依赖项数组),用于异步操作。
依赖项数组是可选的,不传入数组时,回调函数会在每次渲染后执行,传入空数组时,回调函数只会在组件挂载和卸载时执行。依赖项数组可以接受任意数量的值,这意味着对于依赖项数组中更改的任何值,useEffect
方法将再次运行。
useEffect
箭头函数支持返回一个函数,该函数会在组件卸载时执行,用于清理定时器、取消事件监听等。
通常在组件挂载之前进行 API 调用时,会使用 useEffect
。
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
useLayoutEffect
是 React Hooks 提供的一个用于执行副作用操作的 Hook,它与 useEffect 相似,但有一些区别。
和 useEffect
一样,useLayoutEffect
也会在组件渲染之后执行,但是它会在浏览器 layout
和 paint
之前同步执行。这意味着 useLayoutEffect
中的任何操作都将在浏览器更新 DOM 之前执行,这使得它适用于需要精确控制渲染结果的情况。
与 useEffect
不同的是,useLayoutEffect
不会异步执行,这意味着它会阻塞渲染过程,直到它完成。因此,它的性能比 useEffect
差,特别是在执行昂贵操作的情况下。
使用 useLayoutEffect
的场景通常是需要在浏览器更新 DOM 前同步计算布局或者执行某些 DOM 操作。如果没有必要进行同步的操作,建议使用 useEffect
来代替,以获得更好的性能和更流畅的用户体验。
import React, { useState, useLayoutEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
useMemo
用于在组件重新渲染时缓存计算结果,它会缓存一个计算的返回值。可用于性能优化,因为它会缓存计算出的值,并在依赖项数组中的值不改变时返回该值。如果这些值发生变化,那么 useMemo
就会重新运行,然后返回新计算出的值。
import React, { useState, useMemo } from "react";
function Example() {
const [count, setCount] = useState(0);
const expensive = useMemo(() => {
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
}, [count]);
return (
<div>
<p>
You clicked {count} times, and sum is {expensive}
</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
useCallback
主要用于避免在每次渲染时都重新创建函数。
在 React 中,当父组件重新渲染时,所有的子组件也会重新渲染。如果子组件的某个函数作为 props
传递给子组件,而父组件重新渲染时,这个函数会被重新创建。这可能会导致不必要的渲染,因为即使没有必要更新组件,子组件也会重新渲染。这时就可以使用 useCallback
来优化性能。
useCallback
接收两个参数:回调函数和一个依赖项数组。当依赖项数组中的任何一个值发生变化时,回调函数就会重新生成。这意味着当 useCallback
返回的函数被传递给子组件时,只有在依赖项变化时才会重新生成。
import React, { useState, useCallback } from "react";
function Example() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<Child onClick={handleClick} />
</div>
);
}
function Child({ onClick }) {
console.log("Child render");
return <button onClick={onClick}>Click me</button>;
}
export default Example;
useRef
用于在函数组件中创建一个持久化的引用变量,该变量的值在组件重新渲染时不会被重置。useRef
返回一个可变的 ref
对象,其 current
属性被初始化为传入的参数(即初始值),可以通过对 current
属性的修改来更新其值。与 useState
的主要区别在于,useState
的状态更新会触发组件重新渲染,而 useRef
的引用更新不会。
例如,可以使用 useRef
存储上一次的状态值,以便在下一次状态更新时进行比较,从而避免不必要的副作用。
useRef
方法主要用于以下两个方面:
指向 DOM 中的一个元素
import React, { useRef } from "react";
function Example() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={handleClick}>Focus the input</button>
</div>
);
}
export default Example;
存储一些不需要触发重新渲染的变量或状态值
import React, { useState, useRef } from "react";
function Example() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
const handleClick = () => {
prevCountRef.current = count;
setCount(count + 1);
};
return (
<div>
<p>
Now: {count}, before: {prevCountRef.current}
</p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
export default Example;
useContext
用于访问在 React.createContext
中创建的上下文对象。它允许在 React 组件之间共享数据,而不需要通过多层逐层 props
传递数据。
useContext
接受一个上下文对象(通过 React.createContext
创建),并返回该上下文的当前值。在组件渲染期间,当上下文的值发生更改时,React 将重新渲染组件。
import React, { useContext, createContext } from "react";
const ThemeContext = createContext("light");
const Comp1 = () => {
const theme = useContext(ThemeContext);
return <div>{theme}</div>;
};
const Comp2 = () => {
const theme = useContext(ThemeContext);
return <div>{theme}</div>;
};
function Example() {
return (
<ThemeContext.Provider value="dark">
<Comp1 />
<Comp2 />
</ThemeContext.Provider>
);
}
export default Example;
可以编写自己的 Hooks,这些 Hooks 是以 use
开头的函数,并且遵循之前提到的 React Hooks 的相同原则。
import React, { useState, useEffect } 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);
};
});
return isOnline;
}
function Example() {
const isOnline = useFriendStatus(0);
if (isOnline === null) {
return "Loading...";
}
return isOnline ? "Online" : "Offline";
}
export default Example;