React组件三大核心属性 - Refs

1,345 阅读4分钟

这是我参与8月更文挑战的第31天,活动详情查看:8月更文挑战

一、refs的理解

组件内的标签可以定义ref属性标识自己,就比如原生中通过document获取dom元素的方法,这里ref获取的是虚拟DOM转换为真实的DOM,而不是获取虚拟DOM。

此属性可以是一个由 React.createRef() 函数创建的对象、或者一个回调函数、或者一个字符串(遗留 API)。当 ref 属性是一个回调函数时,此函数会(根据元素的类型)接收底层 DOM 元素或 class 实例作为其参数。这能够让你直接访问 DOM 元素或组件实例。

为什么是Refs?

refs存放的是所有使用了ref标识的对象

image.png

何时使用 Refs?

下面是几个适合使用 refs 的情况:

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

避免使用 refs 来做任何可以通过声明式实现来完成的事情。

举个例子,避免在 Dialog 组件里暴露 open() 和 close() 方法,最好传递 isOpen 属性。

注意: 不要过度使用Refs

二、字符串形式的ref

定义: 直接在标签中写上ref="xxx"

使用: this.refs.xxx

// 创建组件
class Demo extends React.Component {
    render() {
        return (
            <div>
                <input ref="input1" type="text" placeholder="点击按钮提示数据" />&nbsp;
                <button onClick={this.showData}>点击提示左侧的数据</button>&nbsp;
            </div>
        )
    }

    showData = () => {
        console.log(this);  // Demo实例
        // 解构
        const { input1 } = this.refs;
        console.log('input1 ==>', input1); // DOM
        console.log('value ==>', input1.value); // 输入框的值
    }
}

// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'));

图示 image.png

注意尽量要同名,否则会替换前一个ref

这种形式也被称为过时的API,官网也推荐我们使用回调函数或者createRef()代替。

但是吧,我们也得知道有这东西哈。官网地址请点击

image.png

三、回调函数形式的ref

什么是回调函数?

  1. 自己定义的函数

  2. 自己没有调用

  3. 最后别人帮你调用

我们这里直接使用箭头函数即可,箭头函数本身没有this,往外找是实例对象, 把当前的ref所在的节点当作函数的实参传给了你所定义的那个属性

class Demo extends React.Component {
    render() {
        // 箭头函数没有this,往外找是Demo的实例对象, 把当前的ref所在的节点当作函数的实参传给了input1属性
        return (
            <div>
                <input ref={currentNode => this.input1 = currentNode} type="text" placeholder="点击按钮提示数据"/>&nbsp;    
                <button onClick={this.showData}>点击提示左侧的数据</button>&nbsp;
            </div>
        )
    }

    showData = () => {
        const {input1} = this;
        console.log(this);
        console.log(input1.value);
    }

}

ReactDOM.render(<Demo />, document.getElementById('test'));

图示 image.png

四、createRef创建的ref

React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,不同节点不能使用同一个,是覆盖操作,会替换掉之前的那个ref。


class Demo extends React.Component {

    // React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,不同节点不能使用同一个,是覆盖操作,会替换掉之前的那个ref
    myRef = React.createRef();
    myRef2 = React.createRef();

    render() {
        return (
            <div>
                <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;    
                <button onClick={this.showData}>点击提示左侧的数据</button>&nbsp;
                <input ref={this.myRef2} onBlur={this.blurData} type="text" placeholder="失去提示数据"/>&nbsp;    
            </div>
        )
    }

    showData = () => {
        console.log(this);
        console.log(this.myRef); // {current: input} ---- current不能改,React规定是这样
        console.log('myRef ==> ', this.myRef.current.value)
    }

    blurData = () => {
        console.log('myRef2 ==> ',this.myRef2.current.value)
    }
}

ReactDOM.render(<Demo />, document.getElementById('test'));

图示 image.png

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

五、回调ref中调用次数的问题

1、内联形式的回调

在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。

2、class 的绑定函数

通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,因为class类函数是已经放在了this(实例对象)上,就算就重新渲染this中已经存在了,就不会重新赋值。但是大多数情况下它是无关紧要的。

也就是说这两者区别是有的,但其实没啥大问题。

案例:当切换天气的时候看函数调用了几次?

class Demo extends React.Component {

    state = {
        isHot: true
    }

    render() {
        const { isHot } = this.state;
        return (
            <div>
                <p>今天天气很{isHot ? '炎热' : '凉爽'}</p>
                {/* 内联的回调 */}
                {/*<input ref={currentNode => {this.input1 = currentNode;console.log('currentNode ==>', currentNode);}} type="text" placeholder="点击按钮提示数据"/>&nbsp;*/}
                <input ref={this.saveInput} type="text" placeholder="点击按钮提示数据" />&nbsp;
                <button onClick={this.changeWeather}>点击改变天气</button>&nbsp;
                <button onClick={this.showData}>点击提示左侧的数据</button>&nbsp;
            </div>
        )
    }

    showData = () => {
        const { input1 } = this;
        alert(input1.value)
    }

    // 绑定class类的函数
    saveInput = (currentNode) => {
        this.input1 = currentNode;
        console.log('currentNode ==>', currentNode);
    }

    // 改变天气,存放在实例对象上的函数
    changeWeather = () => {
        const { isHot } = this.state;
        this.setState({
            isHot: !isHot
        })
    }
}

ReactDOM.render(<Demo />, document.getElementById('test'));

图示 image.png

六、小总结

  1. 创建ref的三种方式:分别是字符串形式回调函数createRef()
  2. 命名的时候应尽量避免重复命名,否则会产生一个覆盖操作
  3. 不要过度的使用ref