React中的useImperativeHandle是一個自定義Hooks,它允許我們向子組件暴露一個特定的接口,使得我們可以在父組件中通過ref對象調用子組件中的方法或屬性。其基本用法是:
import { forwardRef, useImperativeHandle } from 'react';
const Child = forwardRef((props, ref) => {
const [value, setValue] = useState(0);
useImperativeHandle(ref, () => ({
increment() {
setValue(value + 1);
}
}));
return (
<div>
Value: {value}
</div>
);
});
const Parent = () => {
const childRef = useRef();
const handleClick = () => {
childRef.current.increment();
};
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>Increment</button>
</div>
);
};
從上面的示例可以看出,useImperativeHandle接受兩個參數:一個ref對象和一個函數,這個函數返回的對象會被合併到ref對象中。我們通常會把ref對象可調用的方法或就算是state值都傳遞給父組件所使用。
一、控制子組件暴露何種接口
useImperativeHandle的第二個參數是一個函數,它返回的對象會被合併到ref對象中。通過返回的對象不同,我們可以控制子組件暴露出何種接口。假設我們在父子組件間需要通過ref對象來控制子組件的2個按鈕:“加1”和“減1”,那麼我們可以在useImperativeHandle中返回一個對象,這個對象分別對應了“加1”和“減1”方法:
import { forwardRef, useImperativeHandle } from 'react';
const Child = forwardRef((props, ref) => {
const [value, setValue] = useState(0);
const increment = () => {
setValue((val) => val + 1);
};
const decrement = () => {
setValue((val) => val - 1);
};
useImperativeHandle(ref, () => ({
increment,
decrement
}));
return (
<div>
Value: {value}
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
</div>
);
});
const Parent = () => {
const childRef = useRef();
const handleIncrement = () => {
childRef.current.increment();
};
const handleDecrement = () => {
childRef.current.decrement();
};
return (
<div>
<Child ref={childRef} />
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
</div>
);
};
這樣,我們就可以通過一個ref對象控制子組件內部的狀態,而不必在子組件外再定義一個狀態管理邏輯了。
二、僅在某些值發生改變時更新接口
useImperativeHandle的第三個參數是一個數組,它的元素是用來判斷哪些依賴發生改變後再更新useImperativeHandle的返回值的。如果值發生變化,那麼useImperativeHandle函數就會重新執行。舉個例子:
import { forwardRef, useImperativeHandle, useState } from 'react';
const Child = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
const [name, setName] = useState('Child');
useImperativeHandle(ref, () => ({
increment() {
console.log(count);
setCount(count + 1);
}
}), [count]);
console.log('Render Child');
return <div>{name}: {count}</div>;
});
const Parent = () => {
const childRef = useRef();
const handleClick = () => {
childRef.current.increment();
};
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>Increment</button>
</div>
);
};
在上面的例子中,在子組件中我們使用了count變量,這個變量是我們想讓useImperativeHandle關注的狀態。當count變化時,useImperativeHandle函數將重新執行,並更新ref所引用的值,這樣我們就不需要再自己手動重新定義一下。
三、控制React組件工作流
在某些情況下,我們需要控制組件的生命周期,避免不必要的state更新。我們可以把所有控制代碼封裝到useImperativeHandle函數中。例如:
import { forwardRef, useImperativeHandle, useState } from 'react';
const Child = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
const [name, setName] = useState('Child');
useImperativeHandle(ref, () => ({
increment() {
console.log(count);
setCount(count + 1);
}
}), [count]);
console.log('Render Child');
return <div>{name}: {count}</div>;
});
const Parent = () => {
const [showChild, setShowChild] = useState(false);
const childRef = useRef();
const handleClick = () => {
childRef.current.increment();
};
return (
<div>
<button onClick={() => setShowChild(!showChild)}>Toggle Child</button>
{showChild && <Child ref={childRef}/>}
<button onClick={handleClick}>Increment</button>
</div>
);
};
在上述代碼中,我們向子組件暴露了一個increment方法,這個方法被父組件控制。當我們點擊“Toggle Child”按鈕時,子組件重新出現在DOM中,並且再次渲染。但是,我們應該只在子組件出現時重新渲染一次子組件,而不是每次父組件中的任何狀態發生變化就更新一次子組件。這個問題可以通過使用useImperativeHandle來解決。實現方法如下:
import { forwardRef, useImperativeHandle, useState, useMemo } from 'react';
const Child = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
const [name, setName] = useState('Child');
useImperativeHandle(ref, () => ({
increment() {
console.log(count);
setCount(count + 1);
}
}), [count]);
console.log('Render Child');
return <div>{name}: {count}</div>;
});
const MemoChild = memo(Child);
const Parent = () => {
const [showChild, setShowChild] = useState(false);
const childRef = useRef();
const handleClick = () => {
childRef.current.increment();
};
const child = useMemo(() => <MemoChild ref={childRef}/>, []);
return (
<div>
<button onClick={() => setShowChild(!showChild)}>Toggle Child</button>
{showChild && child}
<button onClick={handleClick}>Increment</button>
</div>
);
};
在Parent組件中,我們使用了useMemo來 memorize Child組件,並通過 child變量渲染它。在父組件狀態發生變化時,child不會被重新渲染,因為Child組件的props沒有發生變化。只有在我們觸發了“Toggle Child”按鈕並且showChild和child之間的匹配狀態發生變化時,child重新渲染。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/312861.html