React中ref的使用方法
React与Vue中的Ref
React中的ref:
在React中,ref是一种特殊属性,它可以附加到任何组件上。ref的主要作用是允许我们直接访问DOM元素或者组件实例。这通常是为了管理焦点、触发强制动画或集成非React的库所必需的。由于React的数据流通常是单向且自顶向下的,因此ref提供了一种逃避这种模式以直接交互的方法。最常见的ref使用场景包括:
- 管理输入焦点。
- 触发强制动画。
- 集成第三方DOM库。
React的ref并不推荐频繁使用,因为它违背了React的声明式编程原则。不过,在无法避免直接操作DOM或组件实例时,ref是一个非常有用的逃生窗。
Vue中的ref:
在Vue中,ref也是一个特殊的属性。当它用于HTML元素上时,可以用来获取真实的DOM元素;当它用于组件上时,可以用来获取组件实例。这与React中的ref相似,Vue的ref属性同样用于直接访问DOM节点或子组件的实例。然而,Vue中的ref与数据流动无关,它不依赖于Vue的响应式系统更新。在Vue中,通常可以这样使用ref:
1 | <template> |
在上面的例子中,Vue实例的$refs
对象将包含一个名为myInput
的属性,指向对应的DOM元素。
总的来说,React和Vue中的ref都提供了一种访问DOM元素和子组件实例的方式,但在React中经常需要更多的引导来创建和管理ref,如使用React.createRef()
或useRef()
;而在Vue中,ref则与Vue实例的$refs
对象直接关联,使用起来可能更直观简单一些。两者都让开发者可以绕过框架的抽象层直接与DOM元素或组件实例交互,但应该谨慎使用以避免破坏框架的响应式和组件化原则。
React类组件中ref的使用方式
stringRef-ref属性值为字符串
在React中,使用字符串形式的ref
是一个较旧的API,它允许你将ref
赋予一个字符串而不是一个回调函数或createRef
创建的对象。然而,这种字符串ref
的方式已经被认为是过时的,因为它存在一些问题,比如它们不能和React的新功能,如Hooks,一起使用,并且可能会在未来的React版本中被完全移除。
使用方法类似Vue中ref的使用,以下是示例:
1 | /* |
CallbackRef-ref属性值为函数
在React中,ref
可以设置为一个回调函数,这是一种更为推荐的使用refs
的方式。这个回调函数会在组件挂载(mount)或卸载(unmount)时被调用,并且会接收到底层的DOM元素或类组件的实例作为参数。
这里是一个使用函数形式ref
的例子:
1 | /* |
在这个示例中,当组件挂载时,ref
回调函数会被执行,X
参数引用了底层的DOM元素。然后,这个DOM元素被赋值给this.inputDom
,使得你可以在componentDidMount
生命周期方法中,或者在组件的其他任何方法中访问和操作这个DOM元素。
函数形式的ref
有几个优点:
更大的灵活性:函数
ref
让你有更多的掌控权,可以根据组件的挂载/卸载状态来实施相关操作,比如可以在组件卸载时进行一些清理工作。兼容性:函数
ref
兼容React的所有版本,包括最新的函数组件和使用Hooks的代码。简洁性:回调形式的
ref
可以让你在不使用额外属性或不需要创建React.createRef
的情况下,将ref
存储在组件实例的一个属性上。
当然,如果你使用的是React 16.3或更高版本,并且你的组件是类组件,推荐使用React.createRef
来创建ref
。对于函数组件,推荐使用useRef
Hook。这些新的API提供了一种更为清晰和更为“React”的方式来处理refs。
createRef
在React中,createRef
是创建引用(refs)的一种新方式,它被引入在React 16.3版本,用以替代字符串引用和回调形式的refs。createRef
提供了一种更为干净、明确的方式来访问React元素或组件的实例。
使用createRef
,你会在组件的构造函数中创建一个ref,然后将其赋值给组件的属性。当组件被渲染时,React会将对应的DOM元素或组件实例赋值给current
属性。
这里是一个使用createRef
的例子:
1 | import React, { Component } from 'react'; |
在这个组件生命周期中:
- 构造函数中使用
React.createRef
创建了一个ref(this.textInput
)。 - 在
render
方法中,将这个ref(this.textInput
)附加到<input>
元素上。 - 当组件挂载到DOM(
componentDidMount
)后,使用this.textInput.current
来获取DOM元素,并执行操作,例如设置焦点。
使用createRef
的好处是它提供了一种更加一致的方式来管理refs。这是目前类组件中推荐的方式来访问Refs。对于函数组件,则可以使用useRef
Hook来达到类似的目的。
useRef
useRef
是 React 提供的一个 Hook,它在函数组件中用于获取对 DOM 元素或组件实例的引用。与 createRef
不同,它是专门为函数组件设计的,因为在函数组件中没有实例(this
)来附加ref。
useRef
返回一个可变的 ref
对象,其 .current
属性被初始化为传递给 useRef
的参数(初始值)。返回的对象将在组件的整个生命周期内保持不变。
下面是一个使用 useRef
的例子:
1 | import React, { useRef, useEffect } from 'react'; |
在此示例中,useRef
用于创建一个引用 textInput
,该引用连接到输入元素上。在组件首次渲染后,useEffect
钩子被调用,并且 .current
属性被设置为对应的 DOM 节点,这允许你直接操作该 DOM 元素。
useRef
的一个关键特性是在组件的不同渲染之间保持不变。因此,你可以用它来存储任何可变值,这个值在组件的整个生命周期中都不会改变。一个常见的用例是跟踪之前的 props 或 state 的值。
1 | const prevValue = useRef(); |
在这个例子中,prevValue
用于存储 someValue
的前一个值。
记住,对 ref
对象的 .current
属性的修改不会触发组件重新渲染。如果你需要在修改引用时触发重新渲染,可能需要状态钩子 useState
或 useReducer
。
React函数组件中ref的使用方式
函数组件无法直接使用ref属性
在 React 中,不能直接对一个函数组件使用 ref 属性,因为它们没有实例。尝试对一个函数组件添加 ref 将会导致运行时错误。例如,如果尝试下面这种做法,React 将会抛出一个警告:
1 | function MyFunctionalComponent() { |
为了让这种模式工作,必须使用 React 提供的一种特殊方法 React.forwardRef
。这允许函数组件接收一个 ref
,并将其进一步传递给子组件。
例如,如果想让函数组件能够接收一个 ref,可以这样修改它:
1 | const MyFunctionalComponent = React.forwardRef((props, ref) => { |
在这个例子中,MyFunctionalComponent
被定义为一个使用 React.forwardRef
的函数,这使它可以接受一个 ref
作为第二个参数并将其附加到 DOM 元素上。然后,在父组件中,可以像对待类组件一样对待这个函数组件,并将一个 ref
传递给它。
通过ref控制子组件的行为
在 React 中,通常推荐通过 props 来控制子组件的行为,而不是直接调用子组件的方法。不过,如果确实需要在父组件中获取子组件的实例并调用其方法,可以通过 ref
来实现。对于类组件,这比较直接,因为类组件有实例,但对于函数组件,则需要使用 React.forwardRef
以及使用 useImperativeHandle
Hook 来暴露方法。
让我们通过两个例子来说明这一点:
类组件中的子组件:
如果子组件是一个类组件,可以直接给它一个 ref
并调用它的方法:
1 | class ChildComponent extends React.Component { |
函数组件中的子组件:
如果子组件是一个函数组件,需要使用 React.forwardRef
和 useImperativeHandle
:
1 | import React, { useImperativeHandle, forwardRef } from 'react'; |
在这个函数组件的例子中,ChildComponent
使用了 forwardRef
来接收来自父组件的 ref
。然后,它使用 useImperativeHandle
Hook 来定义当父组件使用 ref
调用方法时应该暴露什么方法。这样,ParentComponent
就可以像在类组件例子中那样调用子组件的方法了。
这种方法应当谨慎使用,并保持 React 数据流的单向性,在大多数情况下,应该尝试通过状态提升或者回调函数来解决问题,而不是直接调用子组件的方法。