我在翻阅钩子文档时偶然发现了useRef。

看看他们的例子…

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

看起来useRef可以用createRef代替。

function TextInputWithFocusButton() {
  const inputRef = createRef(); // what's the diff?
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputRef.current.focus();
  };
  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

为什么我需要一个挂钩裁判?useRef为什么存在?


当前回答

区别在于createRef总是会创建一个新的ref。在基于类的组件中,你通常会在构造过程中将ref放在实例属性中(例如:input = createRef())。在函数组件中没有这个选项。useRef负责每次返回与初始呈现时相同的ref。

下面是一个演示这两个函数行为差异的示例应用程序:

import React, { useRef, createRef, useState } from "react";
import ReactDOM from "react-dom";

function App() {
  const [renderIndex, setRenderIndex] = useState(1);
  const refFromUseRef = useRef();
  const refFromCreateRef = createRef();
  if (!refFromUseRef.current) {
    refFromUseRef.current = renderIndex;
  }
  if (!refFromCreateRef.current) {
    refFromCreateRef.current = renderIndex;
  }
  return (
    <div className="App">
      Current render index: {renderIndex}
      <br />
      First render index remembered within refFromUseRef.current:
      {refFromUseRef.current}
      <br />
      First render index unsuccessfully remembered within
      refFromCreateRef.current:
      {refFromCreateRef.current}
      <br />
      <button onClick={() => setRenderIndex(prev => prev + 1)}>
        Cause re-render
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

其他回答

createRef总是返回一个新的ref,通常将其存储为类组件实例上的一个字段。useRef在函数组件实例的每次呈现时返回相同的引用。这是什么允许在渲染之间保持ref的状态,尽管你没有显式地将它存储在任何地方。

在第二个例子中,ref将在每次渲染时重新创建。

为了强调一个目的:

createRef就像return {current: null}一样简单。这是一种以最现代的方式处理ref= prop的方法,就是这样(而基于字符串的方法太神奇了,而基于回调的方法看起来太冗长了)。

useRef在呈现前保留了一些数据,改变它不会导致重新呈现(就像useState那样)。他们之间很少有联系。对于基于类的组件,您所期望的所有内容都到实例字段(这。* =)看起来像是在功能组件中使用useRef实现的候选。

说useCallback工作作为有界类方法(这。handleClick = .....bind(this)),并且可以使用useRef重新实现(但我们肯定不应该重新发明轮子)。

另一个例子是DOM引用,超时/间隔id,任何第三方库的标识符或引用。

另外,我认为React团队最好为useRef选择不同的命名,以避免与createRef混淆。可能使用andkeep或者usePermanent。

ref是一个普通的JS对象{current:}。

React.useRef(initValue) return a ref { current: initValue }
    it is remember ref value across multiple render of function component.
It is advise to use in Function component
    
React.createRef(initValue) also return a ref  { current: initValue }
    it is not remember ref value across multiple render of function components. It is advise to use in class based component

tldr

ref是一个普通的JS对象{current: <some value>}。

createref()是一个返回ref {current: null}的工厂——没有魔法。

useRef(initValue)也返回一个类似React.createRef()的ref {current: initValue}。此外,它在一个函数组件的多个渲染中记住这个引用是持久化的。

使用React就足够了。类组件中的createRef,因为ref对象被赋值给一个实例变量,因此可以在整个组件及其生命周期中访问:

this.myRef = React.createRef(); // stores ref in "mutable" this context (class)

useRef(null)基本上相当于useState(React.createRef())[0] 1。


1将useRef替换为useState + createRef

下面的推文对我很有启发:

useRef()基本上是useState({current: initialValue})[0]。

通过tldr部分的深入了解,我们现在可以进一步得出结论:

useRef(null)基本上是useState(React.createRef())[0]。

上面的代码“滥用”useState来持久化React.createRef()返回的ref。[0]只是选择useState的值部分-[1]将是setter。

useState导致与useRef相反的重新呈现。更正式地说,当通过setter方法设置新值时,React会比较useState的新旧对象引用。如果我们直接改变useState的状态(相对于setter调用),它的行为或多或少会变得与useRef相同,因为不再触发重新渲染:

// Example of mutating object contained in useState directly
const [ref] = useState({ current: null })
ref.current = 42; // doesn't cause re-render

注意:不要这样做!使用优化的useRef API,而不是重新发明轮子。以上是为了说明目的。

区别在于createRef总是会创建一个新的ref。在基于类的组件中,你通常会在构造过程中将ref放在实例属性中(例如:input = createRef())。在函数组件中没有这个选项。useRef负责每次返回与初始呈现时相同的ref。

下面是一个演示这两个函数行为差异的示例应用程序:

import React, { useRef, createRef, useState } from "react";
import ReactDOM from "react-dom";

function App() {
  const [renderIndex, setRenderIndex] = useState(1);
  const refFromUseRef = useRef();
  const refFromCreateRef = createRef();
  if (!refFromUseRef.current) {
    refFromUseRef.current = renderIndex;
  }
  if (!refFromCreateRef.current) {
    refFromCreateRef.current = renderIndex;
  }
  return (
    <div className="App">
      Current render index: {renderIndex}
      <br />
      First render index remembered within refFromUseRef.current:
      {refFromUseRef.current}
      <br />
      First render index unsuccessfully remembered within
      refFromCreateRef.current:
      {refFromCreateRef.current}
      <br />
      <button onClick={() => setRenderIndex(prev => prev + 1)}>
        Cause re-render
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);