react ref
react ref
1. 前言
如果有这么一种情况,我们需要在一个组件获取另一个组件或者某个DOM元素,这个时候我们应该怎么做呢。react中的Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
2. 什么时候需要使用ref
- 管理焦点,文本选择或媒体播放。(点击之后某个input框聚焦)
- 触发强制动画。(触发某个DOM的动画)
- 集成第三方 DOM 库。
3. ref 的使用方式(三种)
-
字符串形式ref
class MyComponent extends React.Component { handleClick() { // 使用原生的 DOM API 获取焦点 this.refs.myInput.focus(); } render() { // 当组件插入到 DOM 后,ref 属性添加一个组件的引用于到 this.refs return ( <div> <input type="text" ref="myInput" /> <input type="button" value="点我输入框获取焦点" onClick={this.handleClick.bind(this)} /> </div> ); } } ReactDOM.render( <MyComponent />, document.getElementById('example') );
这种方式效率太低了,而且存在一些问题,后续可能被废弃,所以不建议使用
-
回调函数的形式
<script type="text/babel" > class Son extends React.Component { render() { return ( <div> <p ref={this.props.ref4}>我是第一个节点</p> <p ref={this.props.ref3}>我是第二个节点</p> </div> ) } } class Father extends React.Component { handleClick() { console.log(this.ref1.value); console.log(this.ref2.value); console.log(this.ref3); console.log(this.ref4); } saveRef2 = (d) => { this.ref2 = d; } render() { return ( <div> <input value = "ref1" ref = {d => {this.ref1 = d}} /> /* 写法一:直接通过内联回调函数的方法进行ref绑定,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。 */ <input value = "ref2" ref = {this.saveRef2} /> /* 写法二:通过在外部定义函数的方式引入进来,这样就不会每次都创建一个函数实例了,这样就可以只执行一次绑定 */ <Son ref3 = {d => this.ref3 = d} /> /* 我们可以通过这种方式在父组件来获取子组件某一个节点的值 */ <Son ref4 = {d => this.ref4 = d} /> <button onClick={this.handleClick.bind(this)}>获取所有的refs</button> </div> ) } } ReactDOM.render(<Father />,document.getElementById('test')) </script>
回调函数的方式有两种写法,详细看注释,这种写法对于普通的dom节点和组件都是适合的
结果:
-
React.createRef()
这种方式也是官方比较推荐的方式。Refs 是使用
React.createRef()
创建的,并通过ref
属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。class AutoFocusTextInput extends React.Component { constructor(props) { super(props); this.textInput = React.createRef(); } componentDidMount() { this.textInput.current.focusTextInput(); } render() { return ( <CustomTextInput ref={this.textInput} /> ); } }
也就是一般来说会有三个步骤:
-
创建一个
ref
-
将一个
ref
赋值给某个组件或者dom
节点class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); // 创建ref } render() { return <div ref={this.myRef} />; // 赋值ref } }
-
使用这个
ref
当 ref 被传递给
render
中的元素时,对该节点的引用可以在 ref 的current
属性中被访问。const node = this.myRef.current; //current属性是react给我们带上的 // React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。
ref 的值根据节点的类型而有所不同:
- 当
ref
属性用于 HTML 元素时,构造函数中使用React.createRef()
创建的ref
接收底层 DOM 元素作为其current
属性。 - 当
ref
属性用于自定义 class 组件时,ref
对象接收组件的挂载实例作为其current
属性。 - 你不能在函数组件上使用
ref
属性,因为他们没有实例。
请注意:我们的
ref
一定会是某个实例,因为函数没有实例,所以不能再函数组件上使用ref
function MyFunctionComponent() { return <input />; } class Parent extends React.Component { constructor(props) { super(props); this.textInput = React.createRef(); } render() { // 这个不会生效,因为函数组件没有实例 return ( <MyFunctionComponent ref={this.textInput} /> ); } }
-
-
useRef()
虽然我们不能直接在函数组件上使用
ref
,但是我们可以在函数内部使用useRef
,因为函数内部可以有DOM节点或者其它的class
组件供我们访问,但是这个时候我们不能使用React.createRef()
,而是需要使用useRef
这个钩子函数。function CustomTextInput(props) { // 这里必须声明 textInput,这样 ref 才可以引用它 const textInput = useRef(null); // 初始值设置为null,后面挂载后再赋值 function handleClick() { textInput.current.focus(); } return ( <div> <input type="text" ref={textInput} /> <input type="button" value="Focus the text input" onClick={handleClick} /> </div> ); }
-
ref转发(React.forwardRef)
Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但其对某些组件,尤其是可重用的组件库是很有用的。
考虑这个渲染原生 DOM 元素
button
的FancyButton
组件:function FancyButton(props) { return ( <button className="FancyButton"> {props.children} </button> ); }
React 组件隐藏其实现细节,包括其渲染结果。其他使用
FancyButton
的组件通常不需要获取内部的 DOM 元素button
的 ref。这很好,因为这防止组件过度依赖其他组件的 DOM 结构。虽然这种封装对类似
FeedStory
或Comment
这样的应用级组件是理想的,但其对FancyButton
或MyTextInput
这样的高可复用“叶”组件来说可能是不方便的。这些组件倾向于在整个应用中以一种类似常规 DOMbutton
和input
的方式被使用,并且访问其 DOM 节点对管理焦点,选中或动画来说是不可避免的。Ref 转发是一个可选特性,其允许某些组件接收
ref
,并将其向下传递(换句话说,“转发”它)给子组件。在下面的示例中,
FancyButton
使用React.forwardRef
来获取传递给它的ref
,然后转发到它渲染的 DOMbutton
:const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); // 你可以直接获取 DOM button 的 ref: const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>;
这样,使用
FancyButton
的组件可以获取底层 DOM 节点button
的 ref ,并在必要时访问,就像其直接使用 DOMbutton
一样。以下是对上述示例发生情况的逐步解释:
- 我们通过调用
React.createRef
创建了一个 React ref 并将其赋值给ref
变量。 - 我们通过指定
ref
为 JSX 属性,将其向下传递给<FancyButton ref={ref}>
。 - React 传递
ref
给forwardRef
内函数(props, ref) => ...
,作为其第二个参数。 - 我们向下转发该
ref
参数到<button ref={ref}>
,将其指定为 JSX 属性。 - 当 ref 挂载完成,
ref.current
将指向<button>
DOM 节点。
注意
第二个参数
ref
只在使用React.forwardRef
定义组件时存在。常规函数和 class 组件不接收ref
参数,且 props 中也不存在ref
。Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。
- 我们通过调用