# React Hooks useRef 使用范围

作者:HerryLo (opens new window)

博客原文链接 (opens new window)

React Class组件中的createRef,与React Hooks函数组件中的useRef相对应。在说useRef之前,就不得不先看下createRef这个API的目的,它是为了解决什么问题?这样我们才能更加的了解 useRef API。

# 了解 createRef API

React.createRef 可以很好的帮助开发者,获取到DOM元素节点实例,也可以帮助我们获取Class组件的组件实例。对于 createRef API 适用情况,在官方文档中已有给出:

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

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

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

// 通过创建createRef,控制input元素节点DOM
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>
    );
  }
}

// 通过创建createRef,控制子class组件this实例
class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
        { /* CustomTextInput 必须是class组件才有效 */ }
      <CustomTextInput ref={this.textInput} />
    );
  }
}

以上就是 createRefAPI 的典型使用方法,不过有点需要注意,通过React.createRef创建的实例,不能挂在函数组件上,因为它们没有实例。如果一定要使用,可以通过props参数的形式进行传递。

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

# useRef 使用

useRef的主要功能与createRef类似,通过声明 useRef变量而后赋值访问DOM节点。不过相对于createRefuseRef功能更加全面和多样。

# 访问DOM节点

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  // `current` 指向已挂载到 DOM 上的文本输入元素
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

通过ref绑定访问DOM节点,DOM节点依然是挂载在current上。这依然是它的核心功能;

# 保存任何可变值

function Timer() {
  const intervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      // ...
    });
    intervalRef.current = id;
    return () => {
      clearInterval(intervalRef.current);
    };
  });
  
  function handleCancelClick() {
    clearInterval(intervalRef.current);
  }
  
  return (
  	// ...
  )
}

useRef对象是一个 current 属性可变且可以容纳任意值的通用容器。请记住,当 ref 对象内容发生变化时,useRef 并_不会_通知,而且每次渲染时都会返回同一个 ref 对象

# ref 回调

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

通过useCallback传递依赖为 [],保证当且仅当组件挂载和卸载时,callback ref 才会被调用。

我们没有选择使用 useRef,因为当 ref 是一个对象时它并不会把当前 ref 的值的 变化 通知到我们。使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。

createRefuseRef 存在明显的差异,不过主体功能基本一致,useRef不止可访问DOM阶段,还可以在 current 属性存储任意变量而不会被渲染修改,ref 回调没有大的变化,依然是通过传入函数,是 ref 功能扩大化,可以做更多的事情。

关注微信公号 更多干货 分享
扫码加V可交流 扫码备注 博客交流 欢迎
上次更新: 4/28/2022, 3:43:39 AM

评 论: