React Ref 基本了解

887 阅读3分钟

Refs 的产生

官方描述: 在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。

Refs 使用场景

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

注:⚠️ 避免使用 refs 来做任何可以通过声明式实现来完成的事情。如:避免在 Modal 组件内暴露 open()close() 方法,可以通过声明属性 visiable 来进行控制。

Ref 的四种方式

  • React v16.3 版本之前使用字符串(string ref)或回调函数(callback ref)的方式
  • React v16.3 版本引入 React.createRef() API 方式
  • React v16.8 版本引入 React.useRef() hooks 方式

注意:⚠️ 我们不建议使用 string 类型的 refs 方式,它存在 一些问题 并已过时可能会在未来的版本被移除。建议用回调函数createRef API 的方式代替。

string ref 字符串方式

使用示例:

import React from 'react';

class App extends React.Component {
    componentDidMount() {
         // 获取实例
        console.log(this.refs.inputRef);
    }
    
    render() {
        return (
            <input ref='inputRef' />
        )
    }
}

callback ref 回调函数方式

使用示例:

import React from 'react';

class App extends React.Component {
    componentDidMount() {
         // 获取实例
        console.log(this.inputRef);
    }
    
    callbackRef() {
       this.inputRef = node;
    }
    
    render() {
        return (
            // 内联回调函数形式
            <input ref={ node => this.inputRef = node } />
            // 组件方法回调函数形式
            <input ref={this.callbackRef} />
        )
    }
}

注意:⚠️ 内联的方式赋值,在组件发生了更新时,ref 都会重新创建, 可以将回调函数作为组件的方法来避免

React.createRef() API 方式

ref 的值根据节点的类型而有所不同:

  • 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
  • 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例(该自定义函数组件或内部的某个节点)作为其 current 属性。
  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。

使用示例:

import React from 'react';

class App extends React.Component {
    construct(props) {
        super(props);
        // 创建 Refs
        this.inputRef = React.createRef();
        this.childRef = React.createRef();
    }
    
    componentDidMount() {
        // 获取 input 实例
        console.log(this.inputRef.current);
        // 获取 Child 实例
        console.log(this.childRef.current);
    }
    
    render() {
        return (
            <>
                <input ref={this.inputRef} />
                <Child ref={this.childRef} />
            </>
            
        )
    }
}

详细示例

React.useRef() Hook 方式

使用示例:

import React from 'react';

const App = () => {
    const inputRef = React.useRef(null);
    
    React.Effect(() => {
        // 获取实例
        console.log(inputRef.current);
    }, [])
    
    return (
         <input ref={inputRef} />
    )
}

Refs 转发

概念

Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧

如何将 ref 暴露给父组件?

官方说明: 如果你使用 16.3 或更高版本的 React, 这种情况下我们推荐使用 ref 转发。Ref 转发使组件可以像暴露自己的 ref 一样暴露子组件的 ref

React.forwardRef

React.forwardRef 用于 refs 转发,将父组件的 ref 转发给相应的子组件,一般和下面的 useImperativeHandle 配合使用

useImperativeHandle【避免暴露过多属性给父组件,只能用于函数组件】

useImperativeHandle(ref, createHandle, [deps])

类组件使用示例:

  1. 简单示例:
// 子组件
const FancyInput(props, ref) {
    const inputRef = useRef(null); // input 组件实例
  
    useImperativeHandle(ref, () => ({
        focus: () => { inputRef.current.focus() } // 暴露给父组件的方法
    }));
    
    return <input ref={inputRef} ... />;
}

export default React.forwardRef(FancyInput);

export default class Parents extends React.Component {
    handleSubmit = () => {
        console.log(this.testRef); // input 组件实例(只能获取到实例暴露的 focus 方法)
    }

    render() {
        return (
             <FancyInput ref={(dom) => { this.testRef = dom }} />
        )
    }
};
  1. 高阶函数组件示例:
// 子组件
const FancyInput(props) {
    const { forwardRef } = props;
 
    return <input ref={forwardRef} />;
}

// 高阶函数
const HOCFun = (WrapperedComponent) => {
    // 高阶函数内部类组件可以处理一些相同逻辑并返回被包裹的组件 WrapperedComponent
    class Test extends React.Component {

        render() {
            const { forwardRef, ...rest } = this.props;

            return (
                <WrapperedComponent ref={forwardRef} {...rest} />
            )
        }
    }

    // 将父级传递过来的 ref 当作 props 属性传递给子组件 Test
    return React.forwardRef((props, ref) => {
        return <Test forwardRef={ref} {...props} />
    });
}

// 渲染函数返回的组件是经过高阶函数一系列处理后的返回的包裹组件 WrapperedComponent(FancyInput)
const HocComponent = HOCFun(FancyInput);

export default class Parents extends React.Component {
    handleSubmit = () => {
        console.log(this.testRef); // input 组件实例
    }

    render() {
        return (
             <HocComponent ref={(node) => { this.testRef = node }} />
        )
    }
};

函数组件使用示例:

// 子组件
const FancyInput(props, ref) {
    const inputRef = useRef(null);
   
   // 暴露给父组件 Parents 
    useImperativeHandle(ref, () => ({
        focus: () => { inputRef.current.focus(); }
    }));
 
    return <input ref={inputRef} ... />;
}
 
export default forwardRef(FancyInput);
 
// 父组件
const Parents = () => {
     const inputRef = React.useRef(null);
     
     useEffEct(() => {
         console.log(inputRef); // input 组件实例(只能获取到实例暴露的 focus 方法)
     }, [])
     
     return (
         <FancyInput ref={inputRef} />
     )
 } 
 
 export default Parents

参考文章:
Forwarding Refs
Refs and the DOM
Hooks API Reference
React Ref 其实是这样的
React Hooks