Hello everyone, I'm Casson.
React
新文档有个很有意思的细节: useRef
、 useEffect
API
的介绍,在文档中所在的章节Call Escape Hatches
(escape pod).
Obviously, escape pods are not needed during normal navigation, and are only used when encountering danger.
If developers rely too much on these two API
, it may be a misuse.
In React's new documentation: Don't abuse effects , we talked about the correct usage scenario of useEffect
.
Today, let's talk about the usage scenarios of Ref
.
Welcome to join the human high-quality front-end framework group , with flying
Why an escape pod?
Think about a question first: why ref
and effect
are classified as escape pods?
This is because both operate on factors outside of React's control .
effect
deals with side effects . For example: modified ---a2cec4539536ce8526d37d6d0e772585 useEffect
in document.title
.
document.title
React
not belong to the state in ---3e1c6fc17ab1507d9a2a7b628edb30e9---, React
cannot perceive his changes, so it is classified into effect
.
Similarly, to make the DOM focus requires calling element.focus()
, and directly executing DOM API
is also not controlled by React
.
Although they are factors out of React's control , in order to keep the application robust, React
also try to prevent them from getting out of control as much as possible.
Runaway Ref
For Ref
, what is out of control?
First, let's take a look at the situation that is not out of control:
- Execute
ref.current
focus
,blur
and other methods of ---616f75feb5370396cf22a25ca724e006--- - Execute
ref.current.scrollIntoView
to makeelement
scroll into view - execute
ref.current.getBoundingClientRect
measureDOM
dimension
In these cases, although we have operated DOM
, the factors involved are outside the control of React , so it is not out of control.
But the following situation:
- Execute
ref.current.remove
removeDOM
- Execute
ref.current.appendChild
Insert child node
The same operation DOM
, but these are factors within React's control , and executing these operations through ref
is out of control.
As an example, here is an example from the React documentation :
After button 1 is clicked, the P node will be inserted/removed, and after button 2 is clicked, it will call DOM API
to remove the P node:
export default function Counter() {
const [show, setShow] = useState(true);
const ref = useRef(null);
return (
<div>
<button
onClick={() => {
setShow(!show);
}}>
Toggle with setState
</button>
<button
onClick={() => {
ref.current.remove();
}}>
Remove from the DOM
</button>
{show && <p ref={ref}>Hello world</p>}
</div>
);
}
Button 1 removes the P node by React
control.
Button 2 is directly operated DOM
to remove the P node.
If these two methods of removing P nodes are mixed, then clicking button 1 and then button 2 will report an error:
This is the result of the runaway situation caused by manipulating the DOM with Ref .
How to limit runaway
Now the problem comes, since it is called out of control, that is React
uncontrollable ( React
can never restrict developers from using DOM API
right?), then How to limit runaway?
In React
, components can be divided into:
- higher order components
- low-level components
Low-level components refer to those components based on DOM encapsulation , such as the following components, which are directly based on input
node encapsulation:
function MyInput(props) {
return <input {...props} />;
}
In low-level components , you can directly point ---c050f19fac5a7752ba34d0b031718666 ref
to DOM
, for example:
function MyInput(props) {
const ref = useRef(null);
return <input ref={ref} {...props} />;
}
High-order components refer to those components that are packaged based on low-level components , such as the following Form
components, based on Input
component packaging:
function Form() {
return (
<>
<MyInput/>
</>
)
}
Higher-order components cannot directly point --- ref
to DOM
. This limitation controls the scope of ref runaway within a single component, and there will be no ref runaway across components.
Taking the example in the documentation as an example, if we want to click the button in the Form
component and operate input
focus:
function MyInput(props) {
return <input {...props} />;
}
function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
input聚焦
</button>
</>
);
}
After clicking, it will report an error:
Form
d321bfa097fd7b3420398b5d5c747ba5---组件中向MyInput
传递ref
失败了, inputRef.current
input
.
The reason is that, as mentioned above, in order to control the out-of-control scope of ref within a single component, React does not support passing ref across components by default .
Artificial cancellation of restrictions
If you must remove this restriction, you can use forwardRef API
to explicitly pass ref
:
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
After using forwardRef
( forward
here is the meaning of passing ), you can pass ref
across components.
In the example, we pass inputRef
from Form
across components into MyInput
and associate with input
In practice, some students may feel that forwardRef
this API
is a bit unnecessary.
But from the point of view of ref runaway , the intention of forwardRef
is obvious: since the developer manually calls forwardRef
to break the restriction that prevents ref from getting out of control , then he should know what he is doing, You should also take the corresponding risk.
At the same time, with the existence of forwardRef
, it is easier to locate errors after ref-related errors occur.
useImperativeHandle
In addition to restricting the transfer of refs across components , there is also a measure to prevent refs from getting out of control , that is useImperativeHandle
, his logic is as follows:
Since the ref is out of control due to using a DOM method that shouldn't be used (like appendChild ), I can limit the ref to only the methods that can be used .
Modify our MyInput
component with useImperativeHandle
63396cc862fd16336702057ddc7d68e9---:
const MyInput = forwardRef((props, ref) => {
const realInputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus() {
realInputRef.current.focus();
},
}));
return <input {...props} ref={realInputRef} />;
});
Now, the Form
component can only get the following data structure through inputRef.current
:
{
focus() {
realInputRef.current.focus();
},
}
It prevents the developer from fetching the DOM through ref, and executing the API that should not be used, resulting in the situation that the ref is out of control.
Summarize
Under normal circumstances, Ref
is rarely used, and it exists as an escape hatch .
To prevent misuse/abuse leading to ref失控
, React
restrictions By default, refs cannot be passed across components .
To get around this limitation, forwardRef
can be used.
To reduce the abuse of ref
to DOM
, you can use useImperativeHandle
restrict the data structure passed by ref
.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。