Useref usage details

Time:2020-12-28

useuseRefIt’s been a while. I’ve combed it recentlyuseRefDetails of the use of.

1、 Motivation

  1. Function components access DOM elements;
  2. Function component before accessing render variables.

Function components are executed every time they are rendered, and the local variables inside the function are usually recreateduseRefYou can access the last rendered variable, similar toInstance variables for class componentseffect.

1.2 function component usagecreateRefNo way?

createRefMain solutionclassIt’s a common practice to call a component constructor only once in a component. If used within a function componentcreateRefWill cause everyrenderWill callcreateRef

function WithCreateRef() {
  const [minus, setMinus] = useState(0);
  //Each render recreates ` Ref`
  const ref = React.createRef(null);

  const handleClick = () => {
    setMinus(minus + 1);
  };

  //It's always ` null here`
  console.log(`ref.current=${ref.current}`)

  useEffect(() => {
    console.log(`denp[minus]>`, ref.current && ref.current.innerText);
  }, [minus]);

  return (
    <div className="App">
      <h1 ref={ref}>Num: {minus}</h1>
      <button onClick={handleClick}>Add</button>
    </div>
  );
}

2、 Use

2.1 basic grammar

See the document

  1. Every renderinguseRefThe return values are unchanged;
  2. ref.currentChange doesn’t make a differencere-render;
  3. ref.currentChanges should be made asSide Effect(because it will affect the next rendering), so it should not be in therenderPhase updatecurrentProperty.

2.2 may notstayrenderUpdate inref.currentvalue

In is there something like instance variables:

Unless you’re doing lazy initialization, avoid setting refs during rendering — this can lead to surprising behavior. Instead, typically you want to modify refs in event handlers and effects.

stayrenderUpdate inrefsWhat’s the problem?
In asynchronous renderingrenderPhases may be executed multiple times.

const RenderCounter = () => {
  const counter = useRef(0);
  
  // counter.current The value of may increase more than once
  counter.current = counter.current + 1;
  
  return (
    <h1>{`The component has been re-rendered ${counter.current} times`}</h1>
  );
}

2.3 surestayrenderUpdate inref.currentvalue

It is also mentioned in is there something like instance variables:

Unless you’re doing lazy initialization, avoid setting refs during rendering — this can lead to surprising behavior. Instead, typically you want to modify refs in event handlers and effects.

Why?lazy initializationBut it can be inrenderUpdate inref.currentValue?
This is the same asuseRefIt is related to the implementation of lazy initialization.

const instance = React.useRef(null)
if (instance.current == null) {
  instance.current = {
    // whatever you need
  }
}

In essence, as long as the guarantee every timerenderWill not cause unexpected effects, can be in theRender stageto updateref.current。 But better not. It’s easy to cause problems,useRefLazy initialization is, after all, a special exception.

2.4 ref.current may notAs other hooks(useMemo, useCallback, useEffect)Dependencies

ref.currentChanging the value of does not causere-renderReactjs doesn’t trackref.currentChanges in the environment.

function Minus() {
  const [minus, setMinus] = useState(0);
  const ref = useRef(null);

  const handleClick = () => {
    setMinus(minus + 1);
  };

  console.log(`ref.current=${ref.current && ref.current.innerText}`)

  // #1 uesEffect
  useEffect(() => {
    console.log(`denp[ref.current] >`, ref.current && ref.current.innerText);
  }, [ref.current]);

  // #2 uesEffect
  useEffect(() => {
    console.log(`denp[minus]>`, ref.current && ref.current.innerText);
  }, [minus]);

  return (
    <div className="App">
      <h1 ref={ref}>Num: {minus}</h1>
      <button onClick={handleClick}>Add</button>
    </div>
  );
}

In this example, after clicking the [add] button twice#1 uesEffectIt will not be executed again, as shown in the figure:
Useref usage details

Cause analysis:
Dependency judgment is inrenderOf stage judgment, occurring inref.currentBefore the updateuseEffectThe effect function of is executed after rendering.

  1. First execution:
    For the first time, there is no brain to execute

    ref.current=null
    denp[ref.current] > Num: 0
    denp[minus]> Num: 0

    And at this pointref.currentbynullSo #1 uesEffectamount touseEffect(() => console.log('num 1'), [null])

  2. Click [add] to execute for the second time
    hereref.currentThe value is<h1>Num: 0<h1>So #1 uesEffectThe final output is:

    ref.current=Num: 0
    denp[ref.current] > Num: 1
    denp[minus]> Num: 1

    here #1 uesEffectamount touseEffect(() => console.log('num 1'), [<h1>Num: 0<h1>])

  3. Click [add] for the third time
    hereref.currentThe value is<h1>Num: 1<h1>So #1 uesEffectDependencies forNo,Change, so #1 uesEffectThe effect function of is not executed, and the final output is:

    ref.current=Num: 1
    denp[minus]> Num: 2

Ifref.currentAs a dependency,eslint-plugin-react-hooksIt will also give an alarm:

React Hook useEffect has an unnecessary dependency: ‘ref.current’. Either exclude it or remove the dependency array. Mutable values like ‘ref.current’ aren’t valid dependencies because mutating them doesn’t re-render the component react-hooks/exhaustive-deps

2.5 refAs other hooks(useMemo, useCallback, useEffect)Dependencies

refIt’s immutable, and there’s no need to rely on it as other hooks.

3、 Principle

Useref usage details
In essence, it is a memory hook, but it can also be used as a data hook, which can be used simplyuseStatesimulationuseRef

const useRef = (initialValue) => {
  const [ref] = useState({ current: initialValue});
  return ref
}

reference resources

Organize notes from GitHub:useRef