useImperative 这个 hook 可以自定义一个对象,暴露给父组件。
这个 hook 一般结合 forwardRef 一起使用,实现父组件去调用子组件中的实例属性 or 方法,进而操作子组件的 DOM。
那么使用 useImperative 和 forwardRef 时如何定义类型呢?
我们通过一个简单栗子 🌰 来学习一下 :
先定义一个组件:
const Countdown = React.forwardRef((props,ref) => {
React.useImperativeHandle(ref, ()=>({
start() {
alert('Start');
}
});
return <div>Countdown</div>
});
在父组件中引用该组件:
function App(){
const cntEl:any = React.useRef(null);
React.useEffect(()=>{
if(cntEl.current){ cuntEl.current.start() }
}, []);
return <Countdown ref={cntEl} />
}
很多人会这样写,把 TypeScript 变成 AnyScript,对于 cuntEl.current.start() 还报的 Property 'start' does not exist on type 'never'. 错误 ,直接 // @ts-ignore 把错误干掉。
既然解决不了问题,那就解决提出问题的人。
我们需要做几件限定类型安全的事情:
- 限定 Props 类型。
- 限定在
useImperativeHandle定义的对象类型。 - 限定
useRef中的类型。
type CountdownProps = {}
type CountdownHandle = {
start: () => void,
}
// 利用 React 提供的 ForwardRefRenderFunction 去限定 CountdownHandle 和 CountdownProps
const Countdown: React.ForwardRefRenderFunction<CountdownHandle, CountdownProps> = (
props,
forwardedRef,
) => {
React.useImperativeHandle(forwardedRef, ()=>({
start() {
alert('Start');
}
});
return <div>Countdown</div>;
}
export default React.forwardRef(Countdown);
import Countdown, { CountdownHandle } from "./Countdown.tsx";
function App() {
// 限定 useRef 的类型为 CountdownHandle,这样调用 start 不会报错
const countdownEl = React.useRef<CountdownHandle>(null);
React.useEffect(() => {
if (countdownEl.current) {
countdownEl.current.start();
}
}, []);
return <Countdown ref={countdownEl} />;
}
记住,一定要利用好 TypeScript,保障好类型安全,避免 AnyScript。