这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战
在之前我们学习了Hook的基础知识,今天让我们来了解一些官方提供的常见的Hook。
基础Hook
useState
const [state,setState]=useState(initialState);
//我们可以看到useState返回一个state,以及更新state的函数。
//在初始化渲染期间返回的状态(state)与传入的第一个参数(initial State)相同。
//setState函数用于更新state。它接收一个新的state值并将组件的一次重新渲染加入队列
setState(newState);
//在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。
注意
React 会确保 setState 函数的标识是稳定的,并且不会在组件重新渲染时发生变化。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 setState。
函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。下面的计数器组件示例展示了 setState 的两种用法:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
“+” 和 “-” 按钮采用函数式形式,因为被更新的 state 需要基于之前的 state。但是“重置”按钮则采用普通形式,因为它总是把 count 设置回初始值。
如果你的更新函数返回值与当前 state 完全相同,则随后的重渲染会被完全跳过。
惰性初始 state
initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
跳过 state 更新
调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。
需要注意的是,React 可能仍需要在跳过渲染前渲染该组件。不过由于 React 不会对组件树的“深层”节点进行不必要的渲染,所以大可不必担心。如果你在渲染期间执行了高开销的计算,则可以使用 useMemo 来进行优化。
useeffect
useEffect(didUpdate);
该 Hook 接收一个包含命令式、且可能有副作用代码的函数。
在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候才执行。
清除 effect
通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。以下就是一个创建订阅的例子:
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
},[props.source]);
useContext
const value = useContext(MyContext);
//接收一个 context 对象(`React.createContext` 的返回值)并返回该 context 的当前值。别忘记 `useContext` 的参数必须是 *context 对象本身*
额外的 Hook
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useRef
const refContainer = useRef(initialValue);
//`useRef` 返回一个可变的 ref 对象,其 `.current` 属性被初始化为传入的参数(`initialValue`)。返回的 ref 对象在组件的整个生命周期内保持不变。
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
在本例中,渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()。