Refs provides a way to access DOM nodes or React elements created in the Render method.
More articles can be read: github.com/YvetteLau/B…
Refs Usage scenario
In some cases, we need to force changes to subcomponents outside of the typical data flow. The subcomponent may be an instance of the React component or a DOM element, for example:
- Manage focus, text selection or media playback.
- Trigger the forced animation.
- Integrate third-party DOM libraries.
Set the Refs
1. createRef
Supports use within function components and class components
CreateRef was introduced in Release 16.3 of Act.
Create the Refs
Create Refs with react.createref () and attach them to the React element via the ref attribute. Typically in constructors, Refs are assigned to instance properties for reference throughout the component.
Access to the Refs
When a ref is passed to an element in render, a reference to that node can be accessed in the ref’s current property.
import React from 'react';
export default class MyInput extends React.Component {
constructor(props) {
super(props);
// Assign to instance attributes
this.inputRef = React.createRef(null);
}
componentDidMount() {
// Get a reference to this node through this.inputref.current
this.inputRef && this.inputRef.current.focus();
}
render() {
// associate ref with the 'inputRef' created in the constructor
return (
<input type="text" ref={this.inputRef}/>)}}Copy the code
The value of ref varies depending on the type of node:
- when
ref
Property is used toHTML
Element is used in the constructorReact.createRef()
To create theref
Receive the underlyingDOM
Element as itscurrent
Properties. - when
ref
Property for a custom class component,ref
Object receives a mount instance of the component as itcurrent
Properties. - Cannot be used on function components
ref
Property because the function component has no instance.
Conclusion: By adding a ref to the DOM, we can get a reference to the DOM node through ref. We can’t use the ref attribute on function components because function components have no instance.
2. useRef
Use only within function components
UseRef was introduced in Act 16.8 and can only be used in function components.
Create the Refs
Create Refs with React.useref () and attach them to the React element via the ref attribute.
const refContainer = useRef(initialValue);
Copy the code
The ref object returned by useRef remains constant throughout the life of the component.
Access to the Refs
When a ref is passed to the React element, a reference to that node can be accessed in the current attribute of the ref.
import React from 'react';
export default function MyInput(props) {
const inputRef = React.useRef(null);
React.useEffect(() = > {
inputRef.current.focus();
});
return (
<input type="text" ref={inputRef} />)}Copy the code
The ref object returned by react.useref () remains the same for the lifetime of the component. Compare this with the react.createref () :
import React, { useRef, useEffect, createRef, useState } from 'react';
function MyInput() {
let [count, setCount] = useState(0);
const myRef = createRef(null);
const inputRef = useRef(null);
// Execute only once
useEffect(() = > {
inputRef.current.focus();
window.myRef = myRef;
window.inputRef = inputRef; } []); useEffect(() = > {
// createRef is false every time except the first time
console.log('myRef === window.myRef', myRef === window.myRef);
// always true [useRef]
console.log('inputRef === window.inputRef', inputRef === window.inputRef);
})
return (
<>
<input type="text" ref={inputRef}/>
<button onClick={()= > setCount(count+1)}>{count}</button>
</>)}Copy the code
3. The callback Refs
Supports use within function components and class components
React supports setting refs by calling refs back. This gives us more fine-grained control over when Refs are set and released.
To use callback refs, you need to pass the callback function data to the REF attribute of the React element. This function takes a React component instance or AN HTML DOM element as an argument and mounts it to the instance property, as shown below:
import React from 'react';
export default class MyInput extends React.Component {
constructor(props) {
super(props);
this.inputRef = null;
this.setTextInputRef = (ele) = > {
this.inputRef = ele; }}componentDidMount() {
this.inputRef && this.inputRef.focus();
}
render() {
return (
<input type="text" ref={this.setTextInputRef}/>)}}Copy the code
React calls the REF callback and passes in the DOM element (or React instance) when the component is mounted, and calls it and passes in NULL when it is unmounted. React ensures that the Refs are up to date before componentDidMount or componentDidUpdate is triggered.
Refs in the form of callbacks can be passed between components.
import React from 'react';
export default function Form() {
let ref = null;
React.useEffect(() = > {
//ref is the input node in MyInput
ref.focus();
}, [ref]);
return (
<>
<MyInput inputRef={ele= > ref = ele} />
{/** other code */}
</>)}function MyInput (props) {
return (
<input type="text" ref={props.inputRef}/>)}Copy the code
4. String Refs (Obsolete API)
Function components don’t support using string refs [support createRef | useRef | callback Ref]
function MyInput() {
return (
<>
<input type='text' ref={'inputRef'} / >
</>)}Copy the code
Class components
Get the React element from this.refs.XXX.
class MyInput extends React.Component {
componentDidMount() {
this.refs.inputRef.focus();
}
render() {
return (
<input type='text' ref={'inputRef'} / >)}}Copy the code
Ref to pass
Before Hook, HOC and render props were the primary means of reusing component logic in React.
Although the convention for higher-order components is to pass all props to the wrapped component, refs are not passed. In fact, refs are not a prop; like keys, they are handled by React.
This can be fixed with the React. ForwardRef (new in React 16.3). Before the react. forwardRef, we can add the forwardedRef to the container component.
The React. ForwardRef before
import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';
const withData = (WrappedComponent) = > {
class ProxyComponent extends React.Component {
componentDidMount() {
//code
}
// One caveat here is that we need to know that this component is a wrapped component
// Pass the ref value to the forwardedRef prop
render() {
const{forwardedRef, ... remainingProps} =this.props;
return (
<WrappedComponent ref={forwardedRef} {. remainingProps} / >)}}// Specify displayName. No static method is copied (the point is not HOC)
ProxyComponent.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
// Copy non-react static methods
hoistNonReactStatic(ProxyComponent, WrappedComponent);
return ProxyComponent;
}
Copy the code
In this example, we pass the ref property value to the wrapped component via the forwardedRef prop using:
class MyInput extends React.Component {
render() {
return (
<input type="text" {. this.props} / >
)
}
}
MyInput = withData(MyInput);
function Form(props) {
const inputRef = React.useRef(null);
React.useEffect(() = > {
console.log(inputRef.current)
})
// When we use MyInput, we need to distinguish whether it is a wrapped component to specify ref or forwardedRef
return (
<MyInput forwardedRef={inputRef} />)}Copy the code
React.forwardRef
Ref forwarding is a technique for automatically passing a Ref through a component to one of its children, allowing some components to receive a Ref and pass it down to the children.
Forward ref to DOM:
import React from 'react';
const MyInput = React.forwardRef((props, ref) = > {
return (
<input type="text" ref={ref} {. props} / >)});function Form() {
const inputRef = React.useRef(null);
React.useEffect(() = > {
console.log(inputRef.current);/ / input node
})
return (
<MyInput ref={inputRef} />)}Copy the code
- call
React.useRef
Created aReact ref
And assign it toref
The variable. - The specified
ref
Is a JSX property and is passed down<MyInput ref={inputRef}>
- The React to pass
ref
给forwardRef
Within the function(props, ref) => ...
As its second parameter. - Forward this down
ref
Parameters to<button ref={ref}>
, specifying it as a JSX property - when
ref
Mount complete,inputRef.current
Point to theinput
DOM node
Pay attention to
The second argument, ref, only exists when the react. forwardRef component is defined. Regular functions and class components do not accept ref arguments, and ref does not exist in props.
Before the react. forwardRef, if we want to pass the ref attribute to the subcomponent, we need to distinguish whether it is a component wrapped by HOC, which causes some inconvenience to use. Let’s refactor using the React. ForwardRef.
import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';
function withData(WrappedComponent) {
class ProxyComponent extends React.Component {
componentDidMount() {
//code
}
render() {
const{forwardedRef, ... remainingProps} =this.props;
return (
<WrappedComponent ref={forwardedRef} {. remainingProps} / >)}}// when we use a component wrapped withData, we just need to pass the ref
const forwardRef = React.forwardRef((props, ref) = > (
<ProxyComponent {. props} forwardedRef={ref} />
));
/ / specify the displayName.
forwardRef.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
return hoistNonReactStatic(forwardRef, WrappedComponent);
}
Copy the code
class MyInput extends React.Component {
render() {
return (
<input type="text" {. this.props} / >
)
}
}
MyInput.getName = function() {
console.log('name');
}
MyInput = withData(MyInput);
console.log(MyInput.getName); // Test whether the static method copy is normal
function Form(props) {
const inputRef = React.useRef(null);
React.useEffect(() = > {
console.log(inputRef.current);// The wrapped component MyInput
})
// When used, just pass ref
return (
<MyInput ref={inputRef} />)}Copy the code
Get instances of child components (wrapped puppet components) in React-redux
In older versions (V4 / V5)
We know that connect has four parameters, and if we want the instance of the subcomponent (puppet component) in the parent component, we need to set the withRef of the fourth parameter options to true. An instance of the puppet component (wrapped component) can then be obtained in the parent component using the getWrappedInstance() method of the container component instance, as shown below:
//MyInput.js
import React from 'react';
import { connect } from 'react-redux';
class MyInput extends React.Component {
render() {
return (
<input type="text" />)}}export default connect(null.null.null, { withRef: true })(MyInput);
Copy the code
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import MyInput from './MyInput';
function reducer(state, action) {
return state;
}
const store = createStore(reducer);
function Main() {
let ref = React.createRef();
React.useEffect(() = > {
console.log(ref.current.getWrappedInstance());
})
return (
<Provider store={store}>
<MyInput ref={ref} />
</Provider>
)
}
ReactDOM.render(<Main />.document.getElementById("root"));
Copy the code
It is important to note here that MyInput must be a class component, because function components have no instances and cannot be retrieved by ref. React – redux source, through to be packaging components increase the ref attribute, getWrappedInstance returns the instance this. Refs. WrappedInstance.
if (withRef) {
this.renderedElement = createElement(WrappedComponent, { ... this.mergedProps,ref: 'wrappedInstance'})}Copy the code
New version (V6 / V7)
React-redux uses the react. ForwardRef method to forward the ref. The forwardRef (withRef) of option is deprecated as of V6. If you want to get an instance of the wrapped component, you need to set the callback of option 4 to true.
/ / MyInput. Js file
import React from 'react';
import { connect } from 'react-redux';
class MyInput extends React.Component {
render() {
return (
<input type="text" />)}}export default connect(null.null.null, { forwardRef: true })(MyInput);
Copy the code
Add ref directly to the wrapped component to obtain the instance of the wrapped component, as shown below:
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import MyInput from './MyInput';
function reducer(state, action) {
return state;
}
const store = createStore(reducer);
function Main() {
let ref = React.createRef();
React.useEffect(() = > {
console.log(ref.current);
})
return (
<Provider store={store}>
<MyInput ref={ref} />
</Provider>
)
}
ReactDOM.render(<Main />.document.getElementById("root"));
Copy the code
Similarly, MyInput must be a class component, because function components have no instances and cannot be retrieved by ref.
React-redux forwards the REF to the Connect component. Pass to the ref of the WrappedComponent via the forwardedRef.
if (forwardRef) {
const forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
return <Connect {. props} forwardedRef={ref} />
})
forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
return hoistStatics(forwarded, WrappedComponent)
}
/ /...
const{ forwardedRef, ... wrapperProps } = propsconst renderedWrappedComponent = useMemo(
() = > <WrappedComponent {. actualChildProps} ref={forwardedRef} />,
[forwardedRef, WrappedComponent, actualChildProps]
)
Copy the code
Reference links:
- Refs and the DOM
- Refs forward
- Hook API index