前言
Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧,Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。
ref转发
在下述示例中,FancyButton 使用 React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM button,这样,使用 FancyButton 的组件可以获取底层 DOM 节点 button 的 ref ,并在必要时访问,就像其直接使用 DOM button 一样。
import React from "react";
class Refs extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
return (
<FancyButton ref={ref}>Click me!</FancyButton>
);
}
}
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
export default Refs;
- 首先通过调用 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 节点。yao
第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收ref 参数,且 props 中也不存在 ref。Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中
Refs
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。在React中,使用Props是父组件与子组件唯一交互的方式,要修改一个子组件,则需要通过更新Props来重新渲染子组件,但是在某些情况下,可能会需要在数据流范围之外修改子组件,被修改的子组件可能是一个React元素,也可能是一个DOM元素。
-
何时使用Refs(避免使用 refs 来做任何可以通过声明式实现来完成的事情)
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
-
创建Refs
在React 16.3 版本引入了 React.createRef() API,可以通过该API创建一个refs,并通过 ref 属性附加到 React 元素,在构建组件时,通常将ref属性分配给实例属性,这样的话在整个组件中都可以使用。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
- 访问 Refs
当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。ref 的值根据节点的类型而有所不同:-
当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
-
当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
-
不能在函数组件上使用 ref 属性,因为函数组件没有实例。
-
为DOM元素添加ref
React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。class Refs extends React.Component { constructor(props) { super(props); this.state = {}; // 创建一个组件全局可用的refs this.textInput = React.createRef(); } focusTextInput = () => { // 直接使用原生 API 使 text 输入框获得焦点,此时的this.textInput.current就相当于原生的input DOM节点 console.log(this.textInput.current); // <input type="text"> this.textInput.current.focus(); }; render() { // 获取 DOM button 的 ref: // const ref = React.createRef(); return ( <div > {/* 将this.textInput挂载到input上 */} <input type="text" ref={this.textInput} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput}/> {/*<FancyButton ref={ref}>Click me!</FancyButton>*/} </div> ); } }
-
为Class组件添加ref
同上面的例子一样,为InputComponent组件传递ref,但是InputComponent必须是Class声明的组件,不能使用函数方式声明。
父组件:class Refs extends React.Component { constructor(props) { super(props); this.state = {}; // 创建一个组件全局可用的refs this.textInput = React.createRef(); } componentDidMount() { // 组件挂载完毕后会执行ref中的focusTextInput()方法实现自动聚焦 console.log(this.textInput.current); this.textInput.current.focusTextInput(); }; render() { return ( <div > <InputComponent ref={this.textInput}></InputComponent> </div> ); } }
子组件:
class InputComponent extends React.Component { constructor(props) { super(props); this.textInput = React.createRef(); } focusTextInput = () => { this.textInput.current.focus(); }; render() { return ( <div> <input type="text" ref={this.textInput} /> </div> ); } }
-
ref与函数组件
因为函数式组件没有实例,所以不能在函数组件上使用ref属性,但是可以在其内部正常使用ref属性,如:function CustomTextInput(props) { // 必须声明 textInput,这样 ref 才可以引用它 let textInput = React.createRef(); function handleClick() { textInput.current.focus(); } return ( <div> <input type="text" ref={textInput} /> <input type="button" value="Focus the text input" onClick={handleClick} /> </div> ); }
-
回调refs
在上面所描述中,都是通过React的API进行创建ref,React也支持另一种设置refs的方式,即使用回调函数的形式,使用回调的方式中,为ref传递一个函数,这个函数接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。class Refs extends React.Component { constructor(props) { super(props); this.state = {}; // 创建一个组件全局可用的ref this.inputRef = null; // 创建回调函数,为inputRef this.setInputRef = element => { // element就是DOM元素,将其赋给inputRef console.log(element); //<input type="text"> this.inputRef = element; }; this.focusInput = () => { if (this.inputRef) { this.inputRef.focus(); } }; } componentDidMount() { // 组件挂载后,执行focusInput设置,实现自动聚焦 this.focusInput(); }; render() { return ( <div > <input type="text" ref={this.setInputRef} /> </div> ); } }
-
利用这种方式,可以在组件之间传递回调形式的refs,比如通过在父组件中设置一个回调refs,
<CustomTextInput inputRef={el => this.inputElement = el}/>
,通过props传递到子组件中,子组件就可以通过props拿到传递的回调函数<input ref={props.inputRef} />
,并将其赋给ref属性,通过这种方式,父组件中this.inputElement就会设置为与子组件中相对应的DOM节点。
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
总结
综上可以看出,在使用refs时需要根据实际情景看是否有必要,使用的主要场景有:管理焦点,文本选择或媒体播放;触发强制动画;集成第三方 DOM 库。主要是用于传递DOM元素,使用场景并不是很多,但是针对以上三个场景来说使用refs还是有必要的。在创建refs时可以通过React的API进行创建,也可以通过回调Refs函数进行创建,值得注意的时,在函数组件中不可以使用ref属性,但是可以在其内部是可以使用ref的。