The origin of the ref

In a typical React data flow, the props is the only way for the parent component to interact with its children. To modify a child component, you need to re-render it using new props. However, in some cases you need to force changes to child components/elements outside of the typical data flow.

Suitable situations for using Refs:

  • Manage focus, text selection or media playback.
  • Trigger the forced animation.
  • Integrate third-party DOM libraries.

The four ways of ref

Before React V16.3, a ref was retrieved as a string ref or callback ref.

Ref is obtained by character:

// string ref class MyComponent extends React.Component { componentDidMount() { this.refs.myRef.focus(); } render() { return <input ref="myRef" />; }}Copy the code

Ref is obtained by the callback function:

// callback ref
class MyComponent extends React.Component {
  componentDidMount() {
    this.myRef.focus();
  }

  render() {
    return <input ref={(ele) => {
      this.myRef = ele;
    }} />;
  }
}
Copy the code

In V16.3, a new API was introduced with the 0017-new-create-ref proposal: React.createref.

Ref from React. CreateRef:

// React.createRef class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } componentDidMount() { this.myRef.current.focus(); } render() { return <input ref={this.myRef} />; }}Copy the code

And, of course, the hooks recently touted by React: useRef

function MyComponent() { const myRef = useRef(null); Const onButtonClick = () => {// 'current' points to the text input element myref.current-focus () mounted to the DOM; }; Return (<> <input ref={myRef} type="text" /> <button onClick={onButtonClick}> focus </button> </>); }Copy the code

The string ref to be removed

React ref: string ref: string ref: string ref “If you currently use this.refs.textInput to access refs, we recommend using callbacks or the createRef API instead.” Why is it so bad?

Originally written by Dan Abramov, one of the authors of React. Released in news.ycombinator.com/edit?id=120…

  1. String ref cannot be combined. For example, if a parent component of a third-party library has already passed a ref to a child component, we cannot add a ref to the child component. Callback references, on the other hand, do not have an owner, so you can write them at any time. Such as:

    /** string Parent extends React.Component {componentDidMount() {this.refs.childref console.log(this.refs); } render() { const { children } = this.props; return React.cloneElement(children, { ref: ‘childRef’, }); }}

    Class App extends componentDidMount() {componentDidMount() {this.refs.child (); } render() { return ( ); }}

  2. The owner of the string ref is determined by the currently executing component. This means using a generic “render callback” mode (e.g. React), the wrong component will have a reference (it will end up defining renderRow on React instead of your component).

    Class MyComponent extends Component {renderRow = (index) => {// String ref attaches to DataTable this return <input ref={‘input-‘ + index} />;

    Return <input ref={input => this['input-' + index] = input} />;Copy the code

    }

    render() { return } }

  3. String ref is not suitable for static analysis such as Flow. Flow can’t guess the magic that the framework can make the string ref “appear” on React, and its type (which may vary). Callback references are friendlier than static analysis.

  4. String ref forces React to track the currently executing component. This is problematic because it keeps the React module stateful and causes strange errors when copying the React module in bundles. During the Reconciliation phase, the React Element is created and updated, and the REF is encapsulated as a closure that waits for the COMMIT phase to execute, which has some impact on React performance.

See the React source code for coerceRef implementation:

Refs [stringRef] = element, which translates it to function ref

If the string ref changes during the update process, it needs to be compared to current-.ref. _stringRef, which records the value of the last render if string ref was used

Owner is at the time of call createElement method to obtain, through ReactCurrentOwner. Current gain, this value will be set before update a component, such as update ClassComponent, will be set before calling render method, The owner can then be retrieved when render is called.

Strong callback ref

React will call the ref callback and pass in the DOM element when the component is mounted and null when it is unmounted. React ensures that the refs are up to date before componentDidMount or componentDidUpdate is triggered.

If the ref callback is defined as an inline function, it is executed twice during the update process, passing in the argument NULL the first time, and then the argument DOM element the second time. This is because a new function instance is created each time it renders, so React clears the old ref and sets the new one. This can be avoided by defining the ref callback function as a class binding function, but in most cases it is irrelevant.

Later the React. CreateRef

React. CreateRef benefits:

  • Compared to callback ref, react. createRef is more intuitive and avoids some understanding problems of callback ref.

CreateRef’s disadvantages:

  1. Performance is slightly lower than callback Ref
  2. It is still less powerful than callback refs, and createRef can’t do anything about composition problems, such as those mentioned in the previous section.

The value of ref varies depending on the type of node:

  • When the ref attribute is used for HTML elements, the ref created with react.createref () in the constructor receives the underlying DOM element as its current attribute.
  • When the REF attribute is used for a custom class component, the REF object receives the component’s mounted instance as its current attribute.
  • By default, you cannot use ref attributes on function components (they can be used inside function components) because they have no instances: If you want to use refs in a function component, you can use forwardRef (which can be used in conjunction with useImperativeHandle) or you can convert the component to a class component.

Hooks family useRef

How is this fourth way of using ref different?

UseRef returns a mutable ref object whose.current property is initialized as the passed parameter (initialValue). The ref object returned remains constant throughout the life of the component. And useRef can easily store any mutable values, similar to how instance fields are used in class.

It is these features that make useRef and createRef very different.

You can run the following code:

import React, { useState, useRef, useEffect } from "react"; export default function App() { const [count, setCount] = useState(0); const latestCount = useRef(count); useEffect(() => { latestCount.current = count; }); function handleAlertclick() { setTimeout(() => { alert("latestCount.current:" + latestCount.current + '.. count: ' + count); }, 2000); } return (<div> <p> current count: {count} </p> <button onClick={() => setCount(count + 1)}>count + 1</button> <button onClick={handleAlertclick} </button> </div> ) }Copy the code

Then follow these steps:

  1. Click count + 1 button 5 times in a row
  2. Click the prompt button
  3. After clicking the prompt button, click count + 1 button twice consecutively within 2 seconds
  4. Wait for the Alert pop-up prompt.

Then you’ll get an interesting answer: alert: latestCount.current:7.. Count: 5. You can get the latest values using useRef, but not useState.

See Dan, one of the authors of React, on his blog. Or see the difference between React functional components and class components, not just state and performance!

So is useRef really that good? Not really. And because of that feature, there are a lot of problems.

You can try running the code below, or check it out here

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 /> remember the first renderIndex in reffromuseref.current: {reffromuseref. current} <br /> Failed to remember first render index in reffromcreateref. current: {reffromcreateref. current} <br /> <button onClick={() => setRenderIndex(prev => prev + 1)}> </button> </div>); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);Copy the code

In the above case, clicking on the button reffromuseref. current will always be 1, while renderIndex and ReffromCreateref. current will change with the click event; Unexpected?

Because: useRef does not notify you when the ref object’s contents change. Changing the.current property does not cause component rerendering. If you want to run some code when React binds or unbinds the REF of a DOM node, you need to use callback ref.

Summary:

  1. UseRef can get the DOM ref
  2. UseRef can get the latest values
  3. UseRef content changes without notification

I didn’t want to use useRef as the ref method at first because of the above problems.

Refs forward

Do YOU need to expose the DOM Refs to the parent component?

In rare cases, you may want to reference the DOM node of the child node in the parent component. This is generally not recommended because it breaks the encapsulation of the component, but it can occasionally be used to trigger focus or measure the size or position of child DOM nodes.

How do I expose the REF to the parent component?

If you use React 16.3 or later, we recommend using ref forwarding in this case. Ref forwarding enables a component to expose its child’s Ref as well as its own.

What is REF forwarding?

const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); DOM button ref: const ref = react.createref (); <FancyButton ref={ref}>Click me! </FancyButton>;Copy the code

How to forward if in a lower version?

If you use React version 16.2 or lower, or if you need more flexibility than ref forwarding, you can pass directly using ref as a prop with a special name.

Like this:

function CustomTextInput(props) { return ( <div> <input ref={props.inputRef} /> </div> ); } class Parent extends React.Component { constructor(props) { super(props); this.inputElement = React.createRef(); } render() { return ( <CustomTextInput inputRef={this.inputElement} /> ); }}Copy the code

Here’s a step-by-step explanation of what happens in the example above:

  1. We create a React ref and assign it to the ref variable by calling React. CreateRef.
  2. We pass ref down to the JSX attribute by specifying it as a JSX attribute.
  3. React passes ref to the function (props, ref) =>… , as its second parameter.
  4. We forward the ref parameter down to specify it as a JSX property.
  5. When ref is mounted, ref. Current will point to the DOM node.
! [the React Ref is actually such] (https://p6-tt.byteimg.com/origin/pgc-image/6e2ba29a82c646899dc73661b8060fdf?from=pc)