通过本文的阅读,读者将学会如何在
React
中通过高级特性来协同父子组件间的交互。具体来说,将掌握如何利用forwardRef
和useImperativeHandle
钩子在子组件中暴露多个 DOM 节点或方法给父组件。还将了解如何在TS类型安全的前提下,准确地传递和使用这些方法和节点。文章进一步介绍了如何从子组件调用父组件中的方法,以实现组件间的逻辑交互和协作。通过这些技能,读者能够实现更加精细的组件控制和事件处理,提升React
应用的可维护性和用户体验。
子组件将 DOM 节点暴露给父组件
在子组件中使用 forwardRef
将组件包裹,将允许组件使用 ref 将 DOM 节点暴露给父组件。
子组件:
tsx
import {forwardRef} from "react";
// 子组件的Props类型
interface ChildrenProps {
title: string;
}
export const Children = forwardRef<HTMLInputElement, ChildrenProps>((props, ref) => {
return (
<div>
<div>{props.title}</div>
<div><input type="text" ref={ref}/></div>
</div>
);
});
forwardRef
泛型中接收两个参数:
- 第一个参数是
ref
的类型,上述示例中由于ref
绑定到了input
元素节点上,所以类型为HTMLInputElement
- 第二个参数是
props
的类型,上述示例中我们接收title
父组件:
在父组件中调用之前我们创建好的子组件,即可传递 title
与 ref
属性了
tsx
import {Children} from "@/pages/Demo/Children.tsx";
import {useEffect, useRef} from "react";
export const Parent = () => {
const childrenRef = useRef<HTMLInputElement>(null);
useEffect(() => {
console.log(childrenRef.current)
}, []);
return (
<div className={'container-background'}>
<Children title='辰火流光' ref={childrenRef}/>
</div>
);
};
在浏览器中预览:
配合 useImperativeHandle
useImperativeHandle
是 React Hook
之一。使用 useImperativeHandle
,可以让子组件内部显式地指定通过 ref
暴露给父组件的值。
useImperativeHandle
通常和 forwardRef
一起使用,以允许函数组件接收 ref 属性。
使用场景:
- 当我们需要向父组件暴露子组件的某些特定方法时,而不是整个
DOM
节点或组件实例。 - 当我们希望能够控制父组件对子组件 ref 的访问模式,包括隐藏一些内部状态或不希望暴露的方法。
假设子组件中有两个input需要在暴露给外部
子组件代码:
tsx
import React, { forwardRef, useRef, useImperativeHandle } from "react";
// 子组件的Props类型
interface ChildrenProps {
title: string;
}
// 子组件暴露给父组件调用的方法的类型
export interface ChildrenRefs {
inputRef1: React.RefObject<HTMLInputElement>;
inputRef2: React.RefObject<HTMLInputElement>;
}
export const Children = forwardRef<ChildrenRefs, ChildrenProps>((props, ref) => {
// 创建多个ref
const inputRef1 = useRef<HTMLInputElement>(null);
const inputRef2 = useRef<HTMLInputElement>(null);
// 使用useImperativeHandle暴露给父组件的接口
useImperativeHandle(ref, () => ({
// 暴露这些input的ref
inputRef1,
inputRef2,
}));
return (
<div>
<div>{props.title}</div>
<div><input type="text" placeholder='input1' ref={inputRef1}/></div>
<div><input type="text" placeholder='input2' ref={inputRef2}/></div>
</div>
);
});
父组件代码:
tsx
import {Children, ChildrenRefs} from "@/pages/Demo/Children.tsx";
import {useEffect, useRef} from "react";
export const Parent = () => {
const childrenRef = useRef<ChildrenRefs>(null);
useEffect(() => {
console.log(childrenRef.current?.inputRef1.current)
console.log(childrenRef.current?.inputRef2.current)
}, []);
return (
<div className={'container-background'}>
<Children title='辰火流光' ref={childrenRef}/>
</div>
);
};
假设我们不想暴露出整个 <input>
DOM 节点,只想要其中 focus
方法,只需要修改 useImperativeHandle
返回你想要父组件去调用的方法:
tsx
useImperativeHandle(ref, () => ({
inputRef1Focus(){
inputRef1.current?.focus()
},
inputRef2,
}));
父组件修改以下代码:
tsx
useEffect(() => {
console.log(childrenRef.current?.inputRef1Focus())
console.log(childrenRef.current?.inputRef2.current)
}, []);
子组件调用父组件的方法
只需要在父组件中传递该方法到子组件中即可
父组件:
tsx
import {Children} from "@/pages/Demo/Children.tsx";
export const Parent = () => {
// 父组件的方法
const parentMethod = () => {
console.log('父组件方法被调用')
}
return (
<div className={'container-background'}>
{/*增加传递父组件的方法*/}
<Children onParentMethod={parentMethod} />
</div>
);
};
子组件:
tsx
// 子组件的Props类型
interface ChildrenProps {
onParentMethod: () => void;
}
export const Children = (props: ChildrenProps) => {
const handleButtonClick=()=>{
props.onParentMethod();
}
return <button onClick={handleButtonClick}>调用父组件的方法</button>
};
总结
本文介绍了如何在 React 中处理父子组件间的交互,具体包括如何通过 forwardRef
和 useImperativeHandle
在子组件中暴露 DOM 节点或特定方法给父组件,并增强了类型安全性。同时,我们也深入探讨了如何在子组件中调用父组件的方法,以便实现复杂的逻辑交互。
对于新手来说,理解父子组件间的交互可能稍有复杂,但一旦掌握,你会发现 React 在组件间的复用和逻辑控制方面的强大之处。实践中,这些特性能大大提高代码的可读性,可维护性甚至可测试性。
希望通过本文的学习,你已经足够熟练掌握了这些内容,能够在自己的项目中流畅地使用和掌握这些知识,构建出更加健壮且易于维护的 React 应用,提升团队的开发效率和代码质量。总的来说,理解并掌握这些特性是每一个 React 开发者的必备知识点和技能,希望本文能为你的学习之路提供帮助。