我们开发场景会遇到:在函数组件中使用 useRef 来创建一个 ref 对象,然后将其绑定到一个元素上,从而获取该元素的引用以便对引用的元素做出各种操作。而当我们想要在父组件中访问子组件的引用时该怎么做呢?
正解就是:通过 React 的 forwardRef API 来实现。
初识forwardRef
forwardRefAPI 允许我们从父组件向子组件传递一个ref,从而让父组件能够访问子组件的 DOM 节点或实例。语法如下:
import React, { useEffect, useRef } from 'react'
function ParentCp() {
useEffect(() => {
console.log('parent', textRef?.current)
}, [])
const testRef = useRef(null)
return (
<div>
<ChildCp ref={testRef} />
</div>
)
}
import React, { forwardRef } from 'react'
function ChildCp(props, ref) {
useEffect(() => {
console.log('ref', ref?.current)
}, [])
return (
<div>
<div ref={ref}>测试forwardRef</div>
</div>
)
}
export default forwardRef(ChildCp)
示例结果
如何去运用这个API呢
具体场景一、
- 父组件:A(页面级别组件) 子组件: B(子组件 里面有 很多内容 N个输入框)
- 用户打开Modal弹窗,我就要聚焦到弹窗里面的第一个
<Input />
解决办法
- 我们可以使用
forwardRef将子组件包装起来时,我们可以通过forwardRef的第二个参数ref访问子组件。然后,将该ref传递给子组件中需要引用的元素或组件,从而使父组件能够访问子组件的实例或 DOM 节点。这使得forwardRef成为一种非常强大和灵活的工具,可以帮助我们轻松地访问和操作子组件中的元素或组件。
语法示例
import React, { useEffect, useRef } from 'react'
function ParentCp() {
useEffect(() => {
// 聚焦
console.log('parent', textRef?.current?.focus())
}, [])
const testRef = useRef(null)
return (
<div>
<ChildCp ref={testRef} />
</div>
)
}
import React, { forwardRef } from 'react'
import { Input } from 'antd'
function ChildCp(props, ref) {
useEffect(() => {
console.log('ref', ref?.current)
}, [])
return (
<div>
<Input
ref={ref}
onChange={e => {
console.log('e', e)
}}
/>
</div>
)
}
export default forwardRef(ChildCp)
示例结果
- 自动聚焦
新需求
- 调用子组件里面的某个方法,不是调子组件里面的
<Input /> - 通过上面的结果发现,我们当当通过
forwardRef无法满足 - 接下来,需要配合react 另外一个hooks
useImperativeHandle
初始useImperativeHandle
- 是 React 中的一个钩子函数,它可以暴露一个组件的 ref,从而使得父组件可以调用
子组件的某些方法和属性。
useImperativeHandle接收参数
ref:一个 Ref 对象,通常来说,是从父组件传递过来的。createHandle:一个回调函数,该函数返回一个对象,这个对象的属性和方法会被暴露给父组件。[deps]:可选参数,一个数组,用于指定回调函数的依赖项。当这些依赖项发生变化时,回调函数会被重新执行。如果不指定依赖项,则回调函数只会在组件首次渲染时执行一次。
使用注意事项
在子组件中使用 useImperativeHandle 钩子函数时,我们需要将 ref 从父组件传递过来,并在回调函数中返回一个对象。这个对象中的属性和方法会被暴露给父组件以供使用。需要注意的是,只有在回调函数中返回的对象属性和方法才会暴露出去,而子组件中的其他属性和方法则不会。
语法示例
import React, { useEffect, useRef } from 'react'
function ParentCp() {
useEffect(() => {
// 调用子组件里面的 log方法
console.log('parent', textRef?.current?.log())
console.log('parent', textRef?.current?.log2())
}, [])
const testRef = useRef(null)
return (
<div>
<ChildCp ref={testRef} />
</div>
)
}
import React, { forwardRef,useImperativeHandle } from 'react'
function ChildCp(props, ref) {
useImperativeHandle(ref, () => ({
// 将log方法暴露出去
log,
}))
const log = () => {
console.log('log该方法暴露给父组件')
}
const log2 = () => {
console.log('log2该方法没有暴露给父组件')
}
useEffect(() => {
console.log('ref', ref?.current)
}, [])
return (
<div>
<div>useImperativeHandle && forwardRef</div>
</div>
)
}
export default forwardRef(ChildCp)
示例结果
- 调用暴露出去的log
- 调用没有暴露出去的log2 页面报错
总结
- 通过使用
forwardRef,我们可以轻松地将ref对象传递到子组件中,从而可以访问子组件的实例或者 DOM 节点,执行一些操作。 useImperativeHandle钩子函数可以让我们很方便地暴露子组件中的方法和属性,从而让父组件更加灵活的操作子组件。
遗漏
- 在高阶组件中使用
forwardRef
代码示例
import React, { forwardRef, useEffect, useRef, useImperativeHandle } from 'react'
import { Input } from 'antd'
// 高阶组件 withWrapper 接收一个组件 WrappedComponent 作为参数,并返回一个由 forwardRef 包裹的新组件
function withWrapper(WrappedComponent) {
return forwardRef((props: any, ref: any) => {
return (
<div>
// ref 绑定到其中HOC组件其中的DOM节点
<div ref={ref}>
<WrappedComponent {...props} />
</div>
<div>
<div>sss</div>
</div>
</div>
)
})
}
function ChildComponent({ text, children }: { text: string; children: any }) {
return (
<div>
<div>{text}</div>
<div>{children}</div>
</div>
)
}
function ParentComponent() {
const wrapperRef = useRef(null)
useEffect(() => {
console.log(wrapperRef.current) // <div>...</div>
}, [])
// 将子组件 ChildComponent 作为参数传递给高阶函数 withWrapper,并返回一个新组建 WrappedChildComponent
const WrappedChildComponent = withWrapper(ChildComponent)
// 渲染由高阶组件返回的新组件,并将 ref 对象 wrapperRef 作为一个属性传递给这个新组建 WrappedChildComponent
return (
<div>
<WrappedChildComponent ref={wrapperRef} text="Hello, world!">
<div>children</div>
</WrappedChildComponent>
</div>
)
}
export default ParentComponent
结果示例
这个地方,具体使用,我后续还有待考查,后续再补;如果有大佬明白,请在评论赐教,感谢感谢
我疑惑点在于
为啥再高阶组件里面套ref,有没有具体业务场景
为啥把ref绑定再传进来子组件 ref:{current: null}