The official react documentation describes it this way.

Refs provide a way to allow us to access DOM nodes or React elements created in the render method.

I. A simple example

Let’s look directly at an example of ref usage. Here is a function component that creates a ref using useRef.

1
2
3
4
5
6
7
8
9
const App = () => {
    const myRef = useRef();
    return (
        <div>
            <button onClick={() => myRef.current.focus()}>click</button>
            <input ref={myRef} />
        </div>
    );
}

The App component in the above code renders a button and an input, and when the button is clicked, the input gets the focus. The basic usage of ref can be obtained from the example.

  1. create a ref (reference) using useRef (createRef in class components).
  2. bind the ref to a Dom element or class components. Note that ref cannot be bound to a function component, because a function component is not an instance, and each time a function component is rendered, it simply executes the function.
  3. access the bound dom element or class component via ref.current.

When binding ref to a Dom element, ref.current is the native Dom element, so you can access the dom native methods, such as focus(), through ref.current. When binding ref to a class component, ref.current is the class component instance, so you can access the component’s props, states and methods via ref.current. again ref cannot be bound to a function component.

II. Binding ref to class component

Please see the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Child extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 1
        }
    }
    increase() {
        this.setState((state) => {
            return {
                count: state.count + 1
            }
        })
    }
    render() {
        return (
            <div>{this.state.count}</div>
        );
    }
}
const App = () => {
    const myRef = useRef();
    return (
        <div>
            <button onClick={() => myRef.current.increase()}>click</button>
            <Child ref={myRef} />
        </div>
    );
}

Above, we have defined a Child class component, which has a Count in its state and contains an increase method. After we bind myRef to the Child component in the app, we can access the Child’s state and methods through myRef.current, so when we click the button, myRef.current.create() actually executes the Child’s create method.

iii. how does ref bind to a function component?

As mentioned above, ref cannot be bound to a function component because rendering a function component does not create an instance of the function component. how to bind ref to a function component then. There are two cases where it may be necessary to bind a ref to a function component.

  1. pass the ref down to bind to the native dom or class componet inside the function component.
  2. accessing methods or variables declared in the function component via ref.

In this case, you need to use a forwardRef. So the name, the meaning, fowardRef is to pass the ref forward (downward).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const Child = forwardRef((props, ref) => {
    return (
        <input ref={ref} />
    )
});
const App = () => {
    const myRef = useRef();
    return (
        <div>
            <button onClick={() => myRef.current.focus()}>click</button>
            <Child ref={myRef} />
        </div>
    );
}

forwarRef accepts a function component and returns a new function component that can be bound by ref, and the function component can be passed ref internally.In the above example, ref is rebinded to the <input> element inside the child component.So in the app, myRef.current is actually binding to the <input> dom element inside the Child component.

The first requirement has been implemented, what about the second? How to access the functions defined in the function component? This is where the useImperativeHandle hook comes into play. See.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const Child = forwardRef((props, ref) => {
    const [count, setCount] = useState(1);
    const increase = () => {
        setCount(count + 1);
    };
    // 暴露函数和变量给父组件
    useImperativeHandle(ref, () => ({
        increase,
        count
    }));
    return (
        <div>{count}</div>
    )
});
const App = () => {
    const myRef = useRef();
    const btnClick = () => {
        console.log(myRef.current.count);
        myRef.current.increase();
    }
    return (
        <div>
            <button onClick={btnClick}>click</button>
            <Child ref={myRef} />
        </div>
    );
}

The above code, through the useImperativeHandle function, binds the ref to the Object returned by the second parameter. So the methods and states defined in the Child function component (and of course the props) can be accessed through myRef.

IV. The difference between useRef and createRef

Usually useRef is used in function components and createRef is used in class components, why exactly? The fundamental difference is that createRef always creates a new ref, so when used in a class component, the createRef statement is usually put into the constructor. Instead of using createRef in a function component, which is a huge waste of memory and inefficient, useRef can be used instead. useRef returns the same ref every time when the function component is rendered (executing the component function).