从父组件使用 React 组件的函数
React 提供了一套强大的工具集,可以有效地将应用程序拆分和模块化,生成更小的、协同工作的组件。这使得我们开发者能够将功能提取出来,并集中管理。
虽然这种架构方式能够带来惊人的灵活性和可维护性,但我们最终都会遇到这种情况:如果我希望一个自定义 React 组件触发其自定义子组件中的一个函数,该怎么办?
使用 React 的forwardRefAPI 和内置钩子useImperativeHandle,这很容易做到!
本教程假设您已具备 React 的基本知识以及如何使用它。
设置我们的组件
为了演示,我们将创建一个简单的组件,它会显示一些颜色选项和一个方框。点击颜色按钮即可更改方框的颜色。
组件的标题和按钮将位于父组件中,而颜色变化框及其颜色更改功能将位于子组件中。效果如下:
import { useState } from 'react'
const Parent = () => {
return (
<div className="flex flex-col gap-4 min-h-screen bg-gray-200 justify-center items-center">
<h2 className="text-gray-500 text-2xl font-bold text-30">What color should the box be?</h2>
<div className="flex justify-between w-80">
<button className="bg-blue-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Blue</button>
<button className="bg-green-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Green</button>
<button className="bg-red-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Red</button>
<button className="bg-yellow-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Yellow</button>
</div>
<Child/>
</div>
)
}
const Child = () => {
const [ color, setColor ] = useState('bg-blue-300')
const changeColor = color => {
setColor(color)
}
return <div className={`w-40 h-40 transition-colors duration-900 ease-in-out rounded-2xl ${color}`}></div>
}
注:我使用TailwindCSS进行一些快速样式设置。
这里没什么特别复杂的,只是渲染了父组件和子组件。子组件有一个函数可以更新其方框的颜色,还有一些状态来保存这个设置。目前按钮没有任何作用,方框是蓝色的……真无聊!让我们让它动起来吧!
注意:我之所以用这个例子,是因为它易于理解和可视化结果。在实际应用中,最好将颜色作为 prop 传递给子组件。
参考儿童
首先,我们需要以某种方式引用子组件才能访问其属性。React 的useRefhook 正好可以做到这一点。要创建对子组件的引用,我们需要从 `<react-hook>` 导入该 hook react,创建一个引用,然后将该引用应用到组件上。
// Added useRef to our imports
import { useState, useRef } from 'react'
const Parent = () => {
// Set up our reference
const boxRef = useRef(null)
return (
<div className="flex flex-col gap-4 min-h-screen bg-gray-200 justify-center items-center">
<h2 className="text-gray-500 text-2xl font-bold text-30">What color should the box be?</h2>
<div className="flex justify-between w-80">
<button onClick={() => boxRef.current.changeColor('bg-blue-300')} className="bg-blue-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Blue</button>
<button onClick={() => boxRef.current.changeColor('bg-green-300')} className="bg-green-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Green</button>
<button onClick={() => boxRef.current.changeColor('bg-red-300')} className="bg-red-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Red</button>
<button onClick={() => boxRef.current.changeColor('bg-yellow-300')} className="bg-yellow-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Yellow</button>
</div>
{/* Apply the reference to our component */}
<Child ref={boxRef}/>
</div>
)
}
现在我们已经建立了一个引用,可以访问子组件的属性。这个引用有一个名为 `id` 的属性.current,其值设置为它所附加组件的 DOM 节点的值,从而使其能够访问该组件的属性。
我已经为每个按钮添加了点击事件处理程序,以便触发changeColor子组件中的函数。一切似乎都已连接好,应该没问题了吧?我们来试一下:
哎呀,爆炸了!💥 发生什么事了?
这样做行不通的原因,也是让这个过程变得棘手的原因,在于ref我们<Child/>组件上的这个属性并不是一个普通的“prop”。React 处理这个属性ref的方式与其他大多数 props 不同,它不会将其作为 props 对象传递给子组件。
forwardRef救援行动
为了使其正常工作,我们需要将引用“转发”给子组件。幸运的是,React 提供了一个很棒的 API,forwardRef可以实现这一点。
要使用此 API,我们需要从 [此处应填写 API 库名称] 导入它react,并将我们的子组件包装在forwardRef函数中。此函数接收 [此处应填写参数名称]props和ref[此处应填写参数名称] 参数,并返回子组件。
// Added forwardRef to the import list
import { forwardRef, useState, useRef } from 'react'
const Child = forwardRef((props, ref) => {
const [ color, setColor ] = useState('bg-blue-300')
const changeColor = color => {
setColor(color)
}
return <div className={`w-40 h-40 transition-colors duration-900 ease-in-out rounded-2xl ${color}`}></div>
})
这会将我们的 ref 传递给子组件,但现在我们需要changeColor通过该 ref 将函数暴露给父组件。为此,我们需要使用useImperativeHandleReact 提供的 hook。这个 hook 接收一个ref参数和一个函数,允许你通过该 ref 将自定义属性暴露给父组件。以下是它的实际应用:
// Added useImperativeHandle to our imports
import { forwardRef, useState, useRef, useImperativeHandle } from 'react'
const Child = forwardRef((props, ref) => {
const [ color, setColor ] = useState('bg-blue-300')
useImperativeHandle(ref, () => ({
changeColor: color => {
setColor(color)
}
}))
return <div className={`w-40 h-40 transition-colors duration-900 ease-in-out rounded-2xl ${color}`}></div>
})
现在我们已经将 ref 转发到子组件,并自定义了暴露给父组件的实例,使其能够访问一个函数,该函数将更新子组件的状态以改变我们盒子的颜色。
保存下来,试试看!
太棒了!我们可以通过父组件访问子组件的“句柄”,并通过该“句柄”公开的函数来更新子组件的状态。
以下是两个已完成的函数的概览:
import { forwardRef, useState, useRef, useImperativeHandle } from 'react'
const Parent = () => {
// Set up our reference
const boxRef = useRef(null)
return (
<div className="flex flex-col gap-4 min-h-screen bg-gray-200 justify-center items-center">
<h2 className="text-gray-500 text-2xl font-bold text-30">What color should the box be?</h2>
<div className="flex justify-between w-80">
<button onClick={() => boxRef.current.changeColor('bg-blue-300')} className="bg-blue-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Blue</button>
<button onClick={() => boxRef.current.changeColor('bg-green-300')} className="bg-green-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Green</button>
<button onClick={() => boxRef.current.changeColor('bg-red-300')} className="bg-red-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Red</button>
<button onClick={() => boxRef.current.changeColor('bg-yellow-300')} className="bg-yellow-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Yellow</button>
</div>
{/* Apply the reference to our component */}
<Child ref={boxRef}/>
</div>
)
}
const Child = forwardRef((props, ref) => {
const [ color, setColor ] = useState('bg-blue-300')
useImperativeHandle(ref, () => ({
changeColor: color => {
setColor(color)
}
}))
return <div className={`w-40 h-40 transition-colors duration-900 ease-in-out rounded-2xl ${color}`}></div>
})
结论
利用 React 的forwardRefAPI 和useImperativeHandleHook,我们可以更灵活地实现更强大的组件交互,进一步增强 React 库的灵活性。虽然本文中的示例略显复杂,给原本简单的组件增加了不必要的复杂性,但这些概念在构建包含 Alerts、Modal 等组件的组件库时非常有用。
非常感谢您的阅读,希望对您有所帮助!
如果你喜欢这篇文章,请务必在Twitter上关注我,以便及时获取我撰写的新文章!
文章来源:https://dev.to/sabinthedev/using-a-react-component-s-function-in-its-parent-5dj2
