React Introductory Series (2) Ref

In the last blog, I roughly recorded some of the content and ideas about the core concepts in the official React doc, many of which are my guesses based on my understanding of Vue.

Continue to read the advanced guide from this blog, the topic of this blog is Ref in React.

The Role of Ref

Refs provides a way for us to access DOM nodes or React elements created in render methods.

In a typical React data stream,props Is the only way for a parent component to interact with a child component. To modify a child component, you need to re-render it using new props. However, in some cases, you need to force modification of a child component outside of the typical data flow. The modified child component may be an instance of a React component or a DOM element. For both cases, React provides a solution.

The above paragraph is taken from the official doc. In simple terms, under normal circumstances, we can only trigger the re-rendering of components by modifying the state or prop. However, Ref provides a new way for you to directly modify custom components or DOM.

When to use

Here are a few situations where refs are suitable for use:

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

Avoid using refs to do anything that can be done with a declarative implementation.

For example, to avoid exposing the open () and close () methods in the Dialog component, it is better to pass the isOpen property.

What is declarative

The above paragraph mentioned the declarative form, so what is the declarative form?

Simply put, it is a programming paradigm, which is equivalent to imperative programming, function programming, and Object Oriented programming.

The so-called declarative expression indicates the purpose, but does not specify how to do it.

Just like this example in the doc, 'open () ’ is a declarative programming paradigm that only specifies the purpose without specifying how to do it. If it is’isOpen = true ', it is imperative, which tells how to do it. The method is to set isOpen to true.

For details, you can take a look: https://segmentfault.com/a/1190000015924762

Do not overuse

You may first think of using refs to “make things happen” in your app. If this is the case, please take a moment to carefully reconsider which component layer the state attribute should be placed in. Usually you will wonder if it is more appropriate to have this state at a higher component level. View 状态提升 To get more examples.

How to Use Ref

Create

Refs are created using’React.createRef () ‘and attached to React elements via the’ref’ attribute. When constructing a component, it is common to assign Refs to instance attributes so that they can be referenced throughout the component.

1
2
3
4
5
6
7
8
9
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}

Access

When ref is passed to an element in render, a reference to that node can be accessed in the ref’s current property.

1
const node = this.myRef.current;

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

  • When the ref attribute is used on HTML elements, the ref created in the constructor function using React.createRef () receives the underlying DOM element as its current attribute.
  • When the ref attribute is used for custom class components, the ref object receives the mounted instance of the component as its current attribute.
    You can’t use the ref property on function components because they don’t have instances.

For

The following code uses’ref 'to store references to DOM nodes:

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
30
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
//Create a ref to store the DOM element of textInput
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}

focusTextInput() {
//Use the native API directly to make the text text box focus
//NOTE: We access the DOM node via "current"
this.textInput.current.focus();
}

render() {
//Tell React we want to associate < input > ref to
//on the'textInput 'created in the constructor
return (
<div>
<input
type="text"
ref={this.textInput} /> <input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}

React will pass a DOM element to the’current ‘property when the component is mounted and a’null’ value when the component is unmounted. The’ref ‘will be updated before the’assemblentDidMount’ or’assemblentDidUpdate 'lifecycle hook is triggered.

From this sentence, we can roughly guess:

For

If we want to wrap the CustomTextInput above to simulate it being clicked immediately after mounting, we can use ref to get the custom input component and manually call its focusTextInput method:

1
2
3
4
5
6
7
8
9
10
11
12
13
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef(); }

componentDidMount() {
this.textInput.current.focusTextInput(); }

render() {
return (
<CustomTextInput ref={this.textInput} /> );
}
}

Note that this is only valid if’CustomTextInput 'is declared as a class:

1
2
class CustomTextInput extends React.Component {  // ...
}

Through this example, we can go back and understand what we just said: when

That is to say, in this’AutoFocusTextInput ‘component, we have created a new ref, although it is also called’textInput’, but this time the’textInput.current ‘points to the real instance of’CustomTextInput’, that is, new

Refs

By default, you cannot use the ref property on function components because they have no instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
function MyFunctionComponent() {  return <input />;
}

class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef(); }
render() {
// This will *not* work!
return (
<MyFunctionComponent ref={this.textInput} /> );
}
}

From this point of view, the above-mentioned custom component actually calls new statement may be problematic.

I think that if it is a direct call to new, there are only two explanations:

If you want to use’ref 'in the function component, you can use forwardRef(Available with useImperativeHandle Used in combination), or the component can be converted into a class component.

Anyway, you can use the ref attribute inside a function component as long as it points to a DOM element or class component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function CustomTextInput(props) {
//textInput must be declared here so that ref can reference it const textInput = useRef (null);
function handleClick() {
textInput.current.focus(); }

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

The forwardRef here will be talked about when the Ref is forwarded.

Will

In rare cases, you may want to reference the DOM node of a sub-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.

Although you can向子组件添加 ref, but this is not an ideal solution because you can only get component instances instead of DOM nodes. Also, it is invalid on the function component.

If you are using React 16.3 or later, we recommend using it in this case ref 转发. ** Ref forwarding enables a component to expose the ref of a subcomponent as if it were its own ref ** About how to expose the DOM node of a subcomponent to a parent component, in ref 转发文档There is a detailed example in.

If you’re using React 16.2 or earlier, or if you need more flexibility than ref forwarding, you can use这个替代方案Pass ref directly as a prop with a special name.

If possible, we do not recommend exposing DOM nodes, but sometimes it can be a lifesaver. Note that this scenario requires you to add some code to the subcomponent. If you have no control over the implementation of the subcomponent, your remaining option is to use findDOMNode(), but in严格模式 It has been abandoned and is not recommended for use.

Callback

React also supports another way to set refs, called “callback refs”. It can help you more finely control when refs are set and unset.

Instead of passing the ref attribute created by createRef (), you pass a function that takes a React component instance or HTML DOM element as a parameter so that it can be stored and accessed elsewhere.

The following example describes a generic paradigm that uses the ref callback function to store a reference to a DOM node in an instance’s properties.

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
30
31
32
class CustomTextInput extends React.Component {
constructor(props) {
super(props);

this.textInput = null;
this.setTextInputRef = element => { this.textInput = element; };
This.focusTextInput = () => { // focus a text text box using the native DOM API
if (this.textInput) this.textInput.focus();
};
}

componentDidMount() {
//After the component is mounted, let the text box automatically get focus
this.focusTextInput();
}

render() {
//use the callback function of'ref 'to store the reference of the text text box DOM node to React
//on an instance (such as this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput} />
</div>
);
}
}

React will call the’ref ‘callback function and pass in the DOM element when the component is mounted, and call it and pass in’null’ when it is unmounted. React will ensure that the refs are up-to-date until’assemblentDidMount ‘or’assemblentDidUpdate’ is triggered.

There are small partners here who may drill into the horns. Why pass in a function and it will be called.

The answer is simple, React source code for the ref attribute is certainly a special treatment, if its value is a function, go back to call it.

If you guess so, then you actually pass in any function, he will execute, do not have to set ref in the function, set a ref in the function passed in the ref property, it should be just a convention.

You can pass refs as callbacks between components, just like you can pass object refs created with React.createRef ().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} /> </div>
);
}

class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el} />
);
}
}

In the above example, ‘Parent’ passes its refs callback function as’inputRef ‘props to’CustomTextInput’, and’CustomTextInput ‘passes the same function as a special’ref’ property to ‘< input >’. As a result, ‘this.inputElement’ in’Parent ‘is set to the DOM node corresponding to the’input’ element in’CustomTextInput '.

About callbacks

If the’ref ‘callback function is defined as an inline function, it will be executed twice during the update process, the first time with the parameter’null’, and then the second time with the parameter DOM element. This is because a new function instance is created on each render, so React empties the old ref and sets the new one. The above problem can be avoided by defining the callback function of the ref as a bound function of the class, but in most cases it is irrelevant.

Ref forwarding

Ref forwarding is a project that will ref The technique of automatically passing through a component to one of its subcomponents. This is usually not necessary for components in most applications. But it is useful for some components, especially reusable component libraries. The most common cases are described below.

Forward

Consider this’FancyButton ‘component that renders the native DOM element’button’:

1
2
3
4
5
6
7
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}

React components hide their implementation details, including their rendering results. Other components that use’FancyButton ‘** usually do not need ** to get the internal DOM element of’button’ refThis is good because it prevents components from becoming overly dependent on the DOM structure of other components.

While this encapsulation is ideal for application-level components like FeedStory or Comment, it can be inconvenient for highly reusable “leaf” components like FancyButton or MyTextInput. These components tend to be used throughout the application in a way similar to regular DOM buttons and inputs, and accessing their DOM nodes is unavoidable for managing focus, selection, or animation.

** Ref forwarding is an optional feature that allows some components to receive a’ref 'and pass it down (in other words, “forward” it) to subcomponents. **

In the following example, ‘FancyButton’ uses’React.forwardRef ‘to get the’ref’ passed to it, and then forwards to the DOM’button 'it renders:

1
2
3
4
5
6
7
8
9
const FancyButton = React.forwardRef((props, ref) => (  
<button ref={ref} className="FancyButton">
{props.children}
</button>
));

//You can directly get the ref of the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

In this way, a component using FancyButton can get the ref of the underlying DOM node button and access it if necessary, just as if it were using the DOM button directly.

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

  1. We created one by calling React.createRef React ref And assign it to the’ref 'variable.
  2. We pass’ref 'down to < FancyButton ref = {ref} > by specifying it as a JSX property.
  3. React passes’ref ‘to function’ (props, ref) = >… ‘in’forwardRef’ as its second argument.
  4. We forward the ref parameter down to < button ref = {ref} >, specifying it as a JSX attribute.
  5. When the ref mount is complete, ‘ref.current’ will point to the ‘< button >’ DOM node.

Attention

Second parameter

Ref