最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why react `useRef` hook stores the objects in `current` property? Why cant it store directly in the ref object? - S

programmeradmin3浏览0评论

Why does the object returned by the useRef hook stores whatever value it's supposed to hold in current property? Why can't we assign something directly into the ref object as shown below:

const sampleRef = useRef([]);

/** why can't we do this... */
sampleRef.push('1');

/** ...instead of this? Why an extra `current` object? */
sampleRef.current.pus('1');

What is the purpose of useRef returning the argument wrapped inside another object with current property?

Why does the object returned by the useRef hook stores whatever value it's supposed to hold in current property? Why can't we assign something directly into the ref object as shown below:

const sampleRef = useRef([]);

/** why can't we do this... */
sampleRef.push('1');

/** ...instead of this? Why an extra `current` object? */
sampleRef.current.pus('1');

What is the purpose of useRef returning the argument wrapped inside another object with current property?

Share Improve this question asked Dec 15, 2019 at 5:33 Johnson CherianJohnson Cherian 6433 gold badges9 silver badges23 bronze badges 1
  • One thing worth notice is that changing the value of the current property of an object IS storing directly in that object. So your question title kinda contradicts with itself. – meandre Commented Feb 26, 2020 at 7:05
Add a comment  | 

4 Answers 4

Reset to default 10

The answer to this question is not specific to React and its hooks system. Mutating an object is just a solution to how you share values between different closures / scopes.

When you call useRef() for your component a "ref object" is created in React internals, tied to that particular instance of your component. Each time useRef() is called across multiple renders, the same "ref object" is returned. Setting current on it is how your store a value to re-access it on next render.

By doing something like

let value = useRef();
value = 1234;

you're throwing away the ref object, replacing it with a new value in your local scope. There's no way React can track that action and update the ref object that is stored in its internals. (In fact React does not track any action to "ref objects" anyway, it just gives them to you. You mutate them, you access them).

But with the current API, you do

const ref = useRef(); // ref is an object that is stored somewhere in React internals
ref.current = 1234; // you update the property of that object

The next time your component renders, React gives you the same ref object, so that you can use the value that you set previously.

As I understood, they made it because they needed to create an object in order to seal the DOM element object(in development mode) and memoize it. As you know if we are going to memoize something, we need to convert it to an object or array.

Reference:


function mountRef<T>(initialValue: T): {current: T} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  if (__DEV__) {
    Object.seal(ref);
  }
  hook.memoizedState = ref;
  return ref;
}

https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js#L916

The react hooks system works with immutable values. Whenever the component is rendered, the hooks are called (for example the useState hook), and they produce a value or two (state and setter function). If this values are changed from the previous values, other hooks might be called (useEffect when the setter function is initialised).

However, sometimes we don't want to react to this changes. We don't really care what is the value as long as it's there, and we don't care if something changes it. For this cases we've got the ref:

The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class.

Whenever you need to store a value, that will be used, but won't cause a re-render, nor cause useMemo, useCallback, useEffect, etc... to recompute, you can set that value via a ref. Since the ref itself will be used as part of hooks dependencies (useMemo(() => {}, [ref]), you can't update it. To enable immutability, a property inside that ref object, can be changed ref.current, without causing the dependant to recompute, since it's the same ref.

I'm trying to answer the root cause of your question, which I interpret to be something like: "It seems that we should not need an intermediate object with a .current property." You're correct. We don't. Whatever the actual reason regarding useRef, I have noticed it is possible to instead do something like the following, which has the kind of syntax you were asking for in your question (the elimination of .current). Please note this is an advanced usage that actually creates a component local cache like useRef, but that doesn't talk to the DOM.:

//note foo is in array brackets by itself
const [foo /*no setFoo here*/] = useState({bar:"baz"});
...
foo.bar="hello"

or

//note foo in brackets by itself
const [foo /*no setFoo here*/] = useState([]);
...
foo.push(1);

This lets us directly mutate foo's properties without using .current. As long as we never call setFoo, mutating foo's properties will not cause a rerender on its own. The value of foo itself is never changed, since it always points to the same object or array.

However, it is possible, just as with any other variables including useRef ones, to cause a useEffect hook to rerun after a rerender if a changed property like foo.bar appears in the 2nd argument array of the useEffect.

I haven't tried let [foo]= useState("whatever") yet. In that case, we'd be altering the actual value of foo, and depending on React to give that altered value back to us on subsequent rerenders, even though we never notified React of the change. Seems sketchy.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论