9
头图

Hello everyone, I'm Casson.

React新文档有个很有意思的细节: useRefuseEffect 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 make element scroll into view
  • execute ref.current.getBoundingClientRect measure DOM 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 remove DOM
  • 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 .


卡颂
3.1k 声望16.7k 粉丝