`useRef`和`createRef`有什么区别?

290
我正在研究Hooks文档时,偶然发现了 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>
    </>
  );
}

为什么我需要使用Ref钩子?useRef有何作用?

7个回答

297

区别在于createRef总是会创建一个新的引用。在类组件中,通常会在构造函数中将引用放置在实例属性中(例如:this.input = createRef())。但在函数组件中没有此选项。useRef会确保在初始渲染时返回相同的引用。

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

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);

Edit 1rvwnj71x3


1
d= (^-^ ) 很好的一点是 ref 不受限制,甚至可以容纳一个简单的数字;但为什么需要 .current(不像 useState 钩子)? 找到原因: 只是为了使 .current 可以按引用传递,就像真正的类字段一样,而无需奇怪的 setter。 (哈哈,我想知道现在函数与真正的类相比慢多少。) - Top-Master

108

简而言之

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

React.createRef() 是一个工厂函数,返回一个 ref { current: null } - 没有任何魔法

useRef(initValue) 也返回一个 ref { current: initValue },类似于 React.createRef()。此外,它记忆化这个 ref,使其在函数组件的多次渲染中保持不变。

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

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

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


1 使用 useState + createRef 替换 useRef

以下tweet启发了我:

useRef() 基本上等同于 useState({current: initialValue })[0]

结合 tldr 部分的见解,我们现在可以进一步得出结论:

useRef(null) 基本上等同于 useState(React.createRef())[0]

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

useStateuseRef 相比会导致重新渲染。更正式地说,当通过其 setter 方法设置新值时,React 会比较旧的和新的对象引用。如果我们直接修改 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,而不是重新发明轮子。上面的示例仅供说明。


"useRef(null) 基本上相当于 useState(() => React.createRef())[0]" 更像是这样,不是吗?否则 React.createRef() 将在每次渲染时运行。 - benface
1
“懒惰的初始状态”主要是为了提高性能(非函数形式的参数将在后续重新渲染中被忽略)。由于示例仅用于说明目的和假设,因此我认为没有函数形式更容易理解。不过这是一个很好的提示。 - ford04

71

createRef总是返回一个新的引用(reference),通常将其作为类组件实例的一个字段进行存储。而useRef会在函数式组件的每次渲染中返回相同的引用。这就允许引用状态在重新渲染时保持不变,尽管你没有明确地将其存储在任何地方。

在您的第二个示例中,该引用将在每次重新渲染时被重新创建。


这是不正确的,你有支持你说法的参考资料吗? - Adeel Imran
6
这里有一位React开发人员的评论,解释了它是如何工作的:https://www.reddit.com/r/reactjs/comments/a2pt15/when_would_you_use_reactcreateref_vs_reactuseref/eb17vz5/。我很想知道您认为这个答案有什么不正确的地方。 - Joe Clay
我在回答这个问题之前看到了那个链接,你分享的链接中哪里提到了这个事实?我找不到啊 :) - Adeel Imran
4
我分享的链接展示了React开发者之一发布的useRef简化实现。它不同于仅仅调用createRef,因为createRef不是一个hook,而且在调用之间不会保留任何状态。Ryan Cogswell的回答也有一个很好的例子来说明它们之间的差异。 - Joe Clay
4
从上下文中我理解到,useRef是一个使用createRef的自定义钩子。感谢您分享这个知识。 - Adeel Imran

6

为了强调一个目的:

createRef 就跟 return {current: null} 一样简单。这是处理大多数现代 ref 属性的方法,而基于字符串的方式太神奇了,基于回调方式看起来太冗长了。

useRef 在渲染前就保留了一些数据,改变它不会导致重新渲染(就像 useState 那样)。这两者很少有关联。在类组件中你所期望的一切都可以通过实例字段(this.* =)来实现,在函数组件中使用 useRef 是一个很好的选择。

可以说 useCallback 的作用就像绑定类方法(this.handleClick = .....bind(this)),并且可以通过 useRef 来重新实现(但我们肯定不应该重复造轮子)。

其他的例子包括 DOM 引用、超时/间隔 ID、任何第三方库的标识符或引用。

补充说明:我认为 React 团队最好选择不同的名称来避免与 createRef 混淆。也许是 useAndKeep,甚至是 usePermanent


1

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

0

在React中,useRef和forwardRef是两个不同的东西,具有不同的用途。

import React, { useRef } from 'react';

const App = () => {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Focus input</button>
    </div>
  );
};

export default App;

另一方面:
import React, { forwardRef } from 'react';

const CustomInput = forwardRef((props, ref) => {
  return (
    <input type="text" ref={ref} />
  );
});

const App = () => {
  const inputRef = React.createRef();

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={handleClick}>Focus
   </div>
)

-3

对于其他答案的补充,这是另一个但重要的补充。

您无法为createRef设置新值。但是您可以为useRef设置。

const ur = useRef();
const cr = createRef();

ur.current = 10; // you can do it, and value is set
cr.current = 10; // you can, but it's no good, it will not change it

4
ref是一个简单的对象,您可以像平常一样更改它的current属性(我刚测试过)。无论是通过useRef还是createRef创建的,都没有关系。 - ford04

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接