-
useRef
-
基本语法
const ref = useRef(initialValue)
-
返回值
useRef返回一个只有一个属性current的对象,它被设置为你传递的 initialValue
。之后你可以把它设置为其他值。如果你把 ref 对象作为一个 JSX 节点的 ref
属性传递给 React,React 将为它设置 current
属性
注:ref.current
属性是可以修改的,当ref.current属性改变的时候,React 不会重新渲染组件,因为ref是 一个普通的js对象。除了初始化外不要在渲染期间写入或者读取 ref.current,可以在
事件处理程序或者useEffect中读取和写入 ref
-
用法一:用ref引用一个值
因为改变 ref 不会触发重新渲染,这意味着 ref 很适合用来存储一些不影响组件视图输出的信息
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
注:通过使用ref,哪怕重新渲染,也能用来存储信息(不像是普通对象,每次渲染都会重置) ;改变它 不会触发重新渲染(不像是 state 变量,会触发重新渲染)
-
用法二:通过ref操作DOM
import { useRef } from 'react'; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Focus the input </button> </> ); }
-
用法三:获取自定义组件的ref(forwardRef)
const inputRef = useRef(null); return <MyInput ref={inputRef} />; //MyInput组件 import { forwardRef } from 'react'; const MyInput = forwardRef(({ value, onChange }, ref) => { return ( <input value={value} onChange={onChange} ref={ref} /> ); }); export default MyInput;
-
useImperativeHandle
-
基本语法
useImperativeHandle(ref, createHandle, dependencies?) //和useRef、forwardRef结合起来使用
作用:能够自定义由ref暴露出来的句柄
ref:从forwardRef函数中获得的第二个参数
createHandle
:该函数无需参数,返回想要暴露的 ref 的句柄,通常会返回一个包含想暴露的方法的对象
-
使用示例
import { forwardRef, useRef, useImperativeHandle } from 'react'; const MyInput = forwardRef(function MyInput(props, ref) { const inputRef = useRef(null); useImperativeHandle(ref, () => { return { focus() { inputRef.current.focus(); }, scrollIntoView() { inputRef.current.scrollIntoView(); }, }; }, []); return <input {...props} ref={inputRef} />; });
-
useCallback
-
基本语法
const cachedFn = useCallback(fn, dependencies)
作用:在组件顶层调用
useCallback
能够在多次渲染中缓存函数
fn:想要缓存的函数。React 将会在初次渲染而非调用时返回该函数。当进行下一次渲染时,如果 dependencies
相比于上一次渲染时没有改变,那么 React 将会返回相同的函数。否则,React 将返回在最新一次渲染中传入的函数,并且将其缓存以便之后使用。React 不会调用此函数,而是返回此函数
dependencies:有关是否更新 fn
的所有响应式值的一个列表。响应式值包括 props、state,和所有在组件内部直接声明的变量和函数。如果没有使用依赖数组,useCallback
每一次都将返回一个新的函数
返回值:在初次渲染时,useCallback
返回传入的 fn
函数。在之后的渲染中, 如果依赖没有改变,useCallback
返回上一次渲染中缓存的 fn
函数,否则返回这一次渲染传入的 fn
-
用法一:跳过组件的重新渲染(和memo结合使用)
默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件,所以只要组件的props发生变化,父组件的更新就会引起子组件的重新渲染。而没有用useCallback定义的函数,在组件更新时都会生成一个不同的函数,若别的state变化,同时有函数作为prop传递给子组件,也会引起子组件的重新渲染,为解决这一问题,可以使用useCallBack和memo结合起来使用跳过重新渲染
//不使用useCallback的情况
function ProductPage({ productId, referrer, theme }) {
// 每当 theme 改变时,都会生成一个不同的函数
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
{/* 这将导致 ShippingForm props 永远都不会是相同的,并且每次它都会重新渲染 */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
//使用useCallback的情况
function ProductPage({ productId, referrer, theme }) {
// 在多次渲染中缓存函数
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // 只要这些依赖没有改变
return (
<div className={theme}>
{/* ShippingForm 就会收到同样的 props 并且跳过重新渲染 */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
注:用memo包裹的组件,如果 props 和上一次渲染时相同,那么该 组件将跳过重新渲染
-
用法二:防止频繁触发useEffect
如果想要在useEffect内部调用函数,那么就要把这个函数作为依赖项,但是每次渲染的时候函数都会改变,这导致每一次渲染useEffect都会调用,解决办法:
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ 仅当 roomId 更改时更改
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ 仅当 createOptions 更改时更改
注:最好消除对函数依赖项的需求,将函数移入 useEffect 内部
useEffect(() => {
function createOptions() { // ✅ 无需使用回调或函数依赖!
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ 仅当 roomId 更改时更改
-
useMemo
-
基本语法
//useMemo(calculateValue, dependencies) const visibleTodos = useMemo( () => filterTodos(todos, tab), //此处的函数应该是一个没有任何参数的纯函数 [todos, tab] );
useMemo的用法和useCallback的用法很类似,不同之处在于useMemo缓存的是函数调用的结果,useCallback缓存的是函数本身 。
React 将会在首次渲染时调用传入的纯函数,在之后的渲染中,如果 dependencies
没有发生变化,React 将直接返回相同值。否则,将会再次调用 calculateValue
并返回最新结果,然后缓存该结果以便下次重复使用。
useMemo同样能跳过组件的重新渲染(当传入给子组件的是变量时),也能记忆对另一个hoook的依赖
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 提醒:依赖于在组件主体中创建的对象
//此处,创建 searchOptions 对象的代码行将在每次重新渲染时运行,导致visibleItems每次也会重新运行
//解决方法:
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ 只有当 text 改变时才会发生改变
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ 只有当 allItems 或 serachOptions 改变时才会发生改变