学习React Hooks系列 - useRef

27,774 阅读4分钟

Ref属性用来获取DOM元素的节点获取子组件的实例

Ref的更新迭代:

1、String Ref

String Ref是个过时的API。因为String类型的Ref存在一些问题,将在未来的某个版本中被遗弃,不建议使用。

使用方式:this.refs.XXX

获取DOM元素节点:

import React, { Component } from 'react';
class App extends Component {
    componentDidMount() {
        console.log('this.refs.XXX');
        console.log(this.refs.h1Ref);
    }
    render() {
        return <h1 ref='h1Ref'>Hello World!</h1>
    }
}
export default App;

打印结果:


获取子组件的实例:

Ref获取子组件的实例后,可以调用子组件上的方法。

import React, { Component } from 'react';
class App extends Component {
    componentDidMount() {
        console.log(this.refs.childRef);
        this.refs.childRef.handleLog(); // Child Component    }
    render() {
        return (
            <div>
                <h1>Hello World!</h1>
                <Child ref='childRef' count='1' />
            </div>
        )
    }
}
class Child extends Component {
    handleLog = () => {
        console.log('Child Component');
    }
    render() {
        const { count } = this.props;
        return <h2>count: { count }</h2>
    }
}
export default App;

打印结果:


2、Callback Ref

Callback Ref能助你更精细地控制何时 refs 被设置和解除,传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数。

React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMountcomponentDidUpdate 触发前,React 会保证 refs 一定是最新的。

使用方式:ref={element => (this.eleRef = element)}

获取DOM元素节点:

import React, { Component } from "react";
class App extends Component {
  componentDidMount() {
    console.log("Callback Ref");
    console.log(this.h1Ref);
  }
  render() {
    return (
      <div>
        <h1 ref={element => (this.h1Ref = element)}>Hello World!</h1>
      </div>
    );
  }
}
export default App;

打印结果:


获取子组件实例:

import React, { Component } from "react";
class App extends Component {
  componentDidMount() {
    console.log("Callback Ref");
    console.log(this.childRef);
    this.childRef.handleLog();
  }
  render() {
    return (
      <div>
        <h1>Hello World!</h1>
        <Child ref={component => (this.childRef = component)} count="1" />
      </div>
    );
  }
}
class Child extends Component {
  handleLog = () => {
    console.log("Child Component");
  };
  render() {
    const { props } = this;
    return <h1>count: {props.count}</h1>;
  }
}
export default App;

打印结果:

3、Create Ref

React 16.3 发布React.createRef(),并且是现在类组件中推荐使用的。

Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。

当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。

React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref会在 componentDidMountcomponentDidUpdate 生命周期钩子触发前更新。

获取DOM元素节点:

import React, { Component, createRef} from "react";
class App extends Component {
  constructor(props) {
    super(props);
    this.h1Ref = createRef();
  }
  componentDidMount() {
    console.log("React.createRef()");
    console.log(this.h1Ref.current);
  }
  render() {
    return <h1 ref={this.h1Ref}>Hello World!</h1>;
  }
}
export default App;

打印结果:


获取子组件的实例:

import React, { Component, createRef } from "react";
class App extends Component {
  constructor(props) {
    super(props);
    this.childRef = createRef();
  }
  componentDidMount() {
    console.log("React.createRef()");
    console.log(this.childRef.current);
    this.childRef.current.handleLog();
  }
  render() {
    return (
      <div>
        <h1>Hello World!</h1>
        <Child ref={this.childRef} count="1" />
      </div>
    );
  }
}
class Child extends Component {
  handleLog = () => {
    console.log("Child Component");
  };
  render() {
    const { props } = this;
    return <h1>count: {props.count}</h1>;
  }
}
export default App;

打印结果:


4、useRef

在函数组件中使用String Ref、Callback Ref、Create Ref会抛出以下错误❌:

Uncaught Invariant Violation: Function components cannot have refs. Did you mean to use React.forwardRef()?

是因为函数组件没有实例,所以函数组件中无法使用String Ref、Callback Ref、Create Ref,取而代之的是useRef

useRef的作用:

  • 获取DOM元素的节点
  • 获取子组件的实例
  • 渲染周期之间共享数据的存储(state不能存储跨渲染周期的数据,因为state的保存会触发组件重渲染)

获取DOM元素的节点

import React, { useEffect, useRef } from 'react';
function App() {
  const h1Ref = useRef();
  useEffect(() => {
    console.log('useRef')
    console.log(h1Ref.current)
  }, [])
  return <h1 ref={h1Ref}>Hello World!</h1>
}
export default App;

打印的结果:


获取子组件的实例:

注: 因为函数组件没有实例,如果想用ref获取子组件的实例,子组件组要写成类组件。

import React, { Component, useEffect, useRef } from 'react';
function App() {
  const childRef = useRef();
  useEffect(() => {
    console.log('useRef')
    console.log(childRef.current)
    childRef.current.handleLog();
  }, [])
  return (
    <div>
      <h1>Hello World!</h1>
      <Child ref={childRef} count="1"/>
    </div>
  )
}
// 因为函数组件没有实例,如果想用ref获取子组件的实例,子组件组要写成类组件
class Child extends Component {
  handleLog = () => {
    console.log('Child Component');
  }
  render() {
    const { count } = this.props;
    return <h2>count: { count }</h2>
  }
}
export default App;

打印结果:


渲染周期之间共享数据的存储

类组件中,state不能存储跨渲染周期的组件,因为state的参数每一次保存都会触发组件的重渲染。

场景:声明一个参数为count = 0 ,组件初始化时count每秒钟加1,直到count > 5时停止增加,清除定时器。

把定时器设置成全局变量使用useRef挂载到current上

import React, { useState, useEffect, useRef } from "react";
function App() {
  const [count, setCount] = useState(0);
  // 把定时器设置成全局变量使用useRef挂载到current上
  const timer = useRef();
  // 首次加载useEffect方法执行一次设置定时器
  useEffect(() => {
    timer.current = setInterval(() => {
      setCount(count => count + 1);
    }, 1000);
  }, []);
  // count每次更新都会执行这个副作用,当count > 5时,清除定时器
  useEffect(() => {
    if (count > 5) {
      clearInterval(timer.current);
    }
  });
  return <h1>count: {count}</h1>;
}
export default App;