添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 不知非攻 超性感的React Hooks(十)useRef
2 0

海报分享

超性感的React Hooks(十)useRef

import {useRef} from 'react';

在函数式组件中,useRef 是一个返回可变引用对象的函数。该对象 .current 属性的初始值为useRef传入的参数 initialVale

返回的对象将在组件整个生命周期中持续存在。

const ref = useRef(initialValue);

通常情况下,useRef有两种用途,

•访问DOM节点,或者React元素•保持可变变量

1

访问DOM节点或React元素

尽管使用React时,我们推荐大家仅仅只关注数据,但也存在一些场景,我们需要去访问DOM节点才能达到目的。例如下面这个例子。

import React, {Component, createRef} from "react";
export default class Demo extends Component {
  textInput = createRef<HTMLInputElement>();
  focusTextInput = () => {
    if (this.textInput.current) {
      this.textInput.current.focus();
  render() {
    return (
        <input type="text" ref={this.textInput} />
        <button onClick={this.focusTextInput}>点击我让input组件获得焦点</button>
}

案例很简单,通过点击别的按钮,让input组件获得焦点。

在函数组件中,我们可以通过useRef达到同样的目的。

import React, {useRef} from "react";
export default function Demo() {
  const inputRef = useRef<HTMLInputElement>(null);
  const focusTextInput = () => {
    if (inputRef.current) {
      inputRef.current.focus();
  return (
      <input type="text" ref={inputRef} />
      <button onClick={focusTextInput}>点击我让input组件获得焦点</button>
}

这个例子使用useRef替换掉了createRef。

接下来思考一个问题,默认支持的input组件拥有 .focus 方法,调用该方法,input组件就能够获得焦点。那如果我们自己要封装一个Input组件,并且也希望该Input组件能够拥有 .focus .blur 方法,我们应该怎么办?

利用React提供的 api forwardRef 就能够达到这个目的。forwardRef方法能够传递ref引用,具体使用如下

// 官网的案例
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

我们也可以使用同样的方式,自定义Input组件。

import React, {forwardRef, useState, ChangeEvent} from 'react';
export interface InputProps {
  value?: string,
  onChange?: (value: string) => any
function Input({value, onChange}: InputProps, ref: any) {
  const [_value, setValue] = useState(value || '');
  const _onChange = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setValue(value);
    onChange && onChange(value);
  return (
      自定义Input组件
      <input value={_value} onChange={_onChange} ref={ref} />
export default forwardRef(Input);

如果我们想要给 .focus 改个名字,或者返回其他额外的属性或者方法,我们可以使用 useImperativeHandle

useImperativeHandle 可以让我们在使用ref时自定义暴露给父组件的实例值。

import React, {useRef, useImperativeHandle, forwardRef, Ref, useState, ChangeEvent} from 'react';
export interface InputProps {
  value?: string,
  onChange?: (value: string) => any
export interface XInput {
  focus: () => void;
  blur: () => void;
  sayHi: () => void
function Input({value, onChange}: InputProps, ref: Ref<XInput>) {
  const inputRef = useRef<HTMLInputElement>(null);
  const [_value, setValue] = useState(value || '');
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current && inputRef.current.focus()
    blur: () => {
      inputRef.current && inputRef.current.blur()
    sayHi: () => {
      console.log('hello, world!');
  const _onChange = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    console.log(value);
    setValue(value);
    onChange && onChange(value);
  return (
      自定义Input组件
      <input value={_value} onChange={_onChange} ref={inputRef} />
export default forwardRef(Input);

使用一下这个Input组件。

import React, { useRef, useState } from "react";
import Input from './components/Input';
import { Button } from "antd-mobile";
const Demo = () => {
  const textInput = useRef<any>(null);
  const [text, setText] = useState('')
  const focusTextInput = () => {
    if (textInput.current) {
      textInput.current.focus();
      textInput.current.sayHi();
  return (
      <Input ref={textInput} onChange={setText} value={text} />
      <Button onClick={focusTextInput}>点击我,input组件获得焦点</Button>
      <div>{text}</div>
export default Demo;

2

在函数组件中,因为每次 re-render 就意味着函数重新执行一次,因此在函数内部保持变量引用是一件我们需要思考的事情。

在前面学习useState时我们知道,使用useState定义变量,可以做到这样的事情,同样的,利用ref的 .current ,也可以。

一个很常见的应用场景就是对于定时器的清除。我们需要确保 setInterval 的执行结果timer的引用,才能准确的清除对应的定时器。

import React, { useRef, useEffect } from 'react';
export default function Timer() {
  const timerRef = useRef<NodeJS.Timeout>();
  useEffect(() => {
    timerRef.current = setInterval(() => {
      console.log('do something');
    }, 1000);
    // 组件卸载时,清除定时器
    return () => {
      timerRef.current && clearInterval(timerRef.current);
  }, []);