react ref

239 阅读4分钟

ref 提供给了开发者可以直接访问 DOM 元素和组件实例的机会。

声明一个 Ref

类组件和函数式组件声明 ref 的方式是不同的,因为函数式组件是不存在实例的;

  1. 在类组件中可以使用 React.createRef() 和回调函数的形式为 DOM 或者组件绑定 Ref,详见:
import "./styles.css";

import React from "react";

// 类组件类型的 ref 的使用

//1. DOM ref

export default class CustomTextInput extends React.Component {

constructor(props) {

    super(props);

    // 创建一个 ref 来存储 textInput 的 DOM 元素

    this.textInput = React.createRef();

    this.focusTextInput = this.focusTextInput.bind(this);

}

focusTextInput() {

    // 直接使用原生 API 使 text 输入框获得焦点

    // 注意:我们通过 "current" 来访问 DOM 节点

    this.textInput.current.focus();

}

render() {

    // 告诉 React 我们想把 <input> ref 关联到

    // 构造器里创建的 `textInput` 上

    return (

        <div>

            <input type="text" ref={this.textInput} />

            <input

            type="button"

            value="Focus the text input"

            onClick={this.focusTextInput}

            />

        </div>

    );

}}

/**

* 2. component ref

* 2.1 可以通过访问子组件实例上的方法,直接执行子组件的函数

* 2.2 可以通过传递 props 的形式控制子组件的状态,让子组件在对应的状态下去执行对应的函数(官方推荐的方法)

*/

export class AutoFocusTextInput extends React.Component {

constructor(props) {

    super(props);

    this.textInput = React.createRef();

    this.handleClick = this.handleClick.bind(this);

    this.state = {

    focus: false

    };

}

handleClick() {

    // 2.1

    this.textInput.current.focusTextInput();

    // 2.2

    // this.setState({

        // focus: true

    // });

}

render() {

    const { focus } = this.state;

    return (

        <div>

            <CustomInput type="text" ref={this.textInput} isFocus={focus} />

            <input

            type="button"

            value="Focus the text input"

            onClick={this.handleClick}

            />

        </div>

    );

}}

class CustomInput extends React.Component {

constructor(props) {

    super(props);

    this.textInput = React.createRef();

    this.focusTextInput = this.focusTextInput.bind(this);

}

// 暴露到实例上的方法

focusTextInput() {

    this.textInput.current.focus();

}

componentDidUpdate() {

    const { isFocus } = this.props;

    console.log("update, isFocus:", isFocus);

    if (isFocus) {

    this.focusTextInput();

    }

}

render() {

    return <input type="text" ref={this.textInput} />;

}}

这两种方式的区别:

  • React.createRef() 声明的 Ref 需要通过 current 访问实例
  • 回调函数声明的可以直接访问实例

相同点:

为了保证每次更新都能拿到组件最新的实例或 DOM,在组件更新的时候都会对 ref 进行重新赋值 ref 回调函数会首先拿到 null,再拿到具体值

  1. 在函数式组件中可以使用 useRef() 声明一个 Ref,需要通过 current 访问实例

useRef() 创建的引用在组件更新的时候不会改变;useRef 的值发生变化的时候也不会通知组件更新

import React, { useRef } from "react";

// 类组件类型的 ref 的使用

//1. DOM ref

export const HookCustomTextInput = () => {

const inputRef = useRef(null);

const focusTextInput = () => {

    inputRef.current.focus();

};

return (

    <div>

        <input type="text" ref={inputRef} />

        <input

            type="button"

            value="Focus the text input"

            onClick={focusTextInput}

        />

    </div>

)};

/**

* 2. 函数式组件没有实例,需要通过 ref 转发的形式进行父子组件 ref 的交互

*/

export const HookAutoFocusTextInput = () => {

const inputRef = useRef(null);

const handleClick = () => {

    // console.log(inputRef.current);

    inputRef.current.focus();

};

return (

    <div>

        <CustomInput type="text" ref={inputRef} />

        <input type="button" value="Focus the text input" onClick={handleClick} />

    </div>

)};

// 使用 React.forwardRef 进行 ref 转发

const CustomInput = React.forwardRef((props, ref) => {

    return <input type="text" ref={ref} />;

});

// 不转发

// const CustomInput = (props) => {

    // return <input type="text" />;

// };

父子组件交互

当父组件想要拿到子组件下组件的实例,有对应交互的需求的时候

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。 类组件和函数式组件的操作方式也是不同的;

类组件

类组件是有实例的,所有在类里面声明的函数都会挂载到组件实例上,那么我们可以通过执行子组件实例上的函数直接操作子组件下的组件;

这种做法是具有破坏性的;

官方推荐是通过状态提升的方式,控制子组件 props 间接操作子组件下组件的状态,详见上面的代码块

函数式组件

函数式组件没有实例,默认情况下,你不能在函数组件上使用 ref 属性。在不使用 ref 转发的情况下,访问函数式组件的 ref 为 null

在函数式组件中可以通过 React.forwardRef((props,ref)=>(<div ref={ref}>refs</div>))的形式将子子组件的 ref 暴露到父组件

react 还提供了 useImperativeHandle(ref,()=>({[key]:value})) 的方式为暴露出去的 ref 绑定额外的属性,在有的时候这个方式非常有用

在写高阶组件的时候也可以通过 React.forwardRef 将 ref 传递给真正的组件:


function logProps(Component) {

class LogProps extends React.Component {

componentDidUpdate(prevProps) {

console.log("old props:", prevProps);

console.log("new props:", this.props);

}

render() {

const { forwardedRef, ...rest } = this.props;

// 将自定义的 prop 属性 “forwardedRef” 定义为 ref

return <Component ref={forwardedRef} {...rest} />;

}

}


// 注意 React.forwardRef 回调的第二个参数 “ref”。

// 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”

// 然后它就可以被挂载到被 LogProps 包裹的子组件上。

return React.forwardRef((props, ref) => {

return <LogProps {...props} forwardedRef={ref} />;

});

}

另外的

在类组件和函数式组件里面除了上面的方式之外,还可以使用除了 ref 之外的参数名,将 ref 传递给子组件,再由子组件去绑定值(作为正常的 props 进行传递)。个人认为这种方式比较 hack,不能直观的表达意图,如果非要用的话,还是要起一个易懂的名字

参考

refs & DOM

refs 转发

useImperativeHandle