React hooks 和功能组件 ref

React hooks and functional component ref

const Comp1 = forwardRef((props, ref) => {
    useImperativeHandle(ref, () => ({
    print: () => {
      console.log('comp1')
    }
  }), []);
  return <div>comp1</div>
});

const Comp2 = () => <div>comp2</div>;

const App = () => {
  const ref1 = useRef(null);
  const ref2 = useRef(null);

  useEffect(() => {
    console.log(ref1); // prints ref1 with the expected current  
    console.log(ref2); // prints ref2 with current: null
  })
  return <div><Comp1 ref={ref1}/><Comp2 ref={ref2}/></div>
}
  1. Comp1 和 Comp2 refs 有什么区别?
  2. 为什么我必须将 forwardRef 与 useImperativeHandle 一起使用才能真正获得对 Comp1 的引用?

https://codepen.io/benma/pen/mddEWjP?editors=1112

我会尝试从问题 2 开始回答

  • useImperativeHandle 描述引用调用语法将与 公开给父级的实例值的一些自定义方法。

  • forwardRef 帮助通过高阶组件转发引用以引用内部 DOM 节点。

当您尝试聚焦时:ref1.current.focus(),子输入将被聚焦,而不是包装子输入的高阶组件。

查看与使用 useImperativeHandle 的 setValue 方法和 ChildInput 中的转发输入相关的答案相关的简单示例: https://codesandbox.io/s/react-hook-useimperativehandle-huktt

React 文档说:

You may not use the ref attribute on function components because they don’t have instances. (more)

这意味着您无法将引用绑定到功能组件。这就是为什么你的 ref2.currentnull。如果要将 ref 绑定到组件,则需要使用 class 组件。您的 ref1 不是 Comp1 组件的引用。它实际上包含一个您在 useImperativeHandle 挂钩中传递的对象。即它包含下一个对象:

{
    print: () => {
      console.log('comp1')
    }
}

如果要将 ref 与组件呈现的某些 HTML 元素或 class 组件绑定,则必须将 forwardRef 与功能组件一起使用。或者您可以使用 useImperativeHandle 钩子将您的 ref 与某个对象绑定。

更新

使用 useImperativeHandle 与向 class 组件添加方法相同:

class Comp1 extends React.Component {
    print() {
        console.log('comp1');
    }

    render() {
        return (<div>comp1</div>)
    }
}

相同
const Comp1 = forwardRef((props, ref) => {
    useImperativeHandle(ref, () => ({
    print: () => {
      console.log('comp1')
    }
  }), []);
  return <div>comp1</div>
});

您在评论中提问:

So after moving to hooks (and still avoid using classes), the only way to use ref is to use useImperativeHandle (and actually use a "fake" ref)? Is this a good practice?

答案: 使用 useImperativeHandle 与在 class 组件中通过引用调用子组件方法是相同的不良做法。 React 文档说你应该避免通过 refs 调用子组件方法,你应该避免使用 useImperativeHandle。此外,您需要避免使用 refs,因为您可以在没有它们的情况下做事。

1. What's the difference between Comp1 and Comp2 refs?

Comp1 使用 React.forwardRef 并且能够从父级接收给定的 ref
Comp2 将不起作用并触发以下错误:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

<Child ref={ref1}/> 就像来自父组件的请求:“嘿 Child,请将您的组件引用或类似内容传递给给定的可变存储框 (ref),以便我可以调用你的方法或直接操作包含的 DOM 节点。"

问题 - 函数组件没有 instance
// React internally calls `new ClassComp()`, instance can be stored and passed in a ref
<ClassComp ref={compRef} />

// React just *calls* the function for re-renders, there is no plain function "instance" 
<FunctionComp ref={compRef} />
React.forwardRef 解决了上述限制。和
const FunctionComp = React.forwardRef((props, ref) => <div ref={ref}>Hello FnComp</div>
尽管没有实例,

FunctionComp 仍然可以将代表某些内容传递给 Parent 给出的 ref(如 div DOM 节点)。 ClassComp 而不是在此处传递它的实例。


2. Why do I have to use forwardRef along with useImperativeHandle in order to actually get the ref to Comp1?

您不必这样做。 useImperativeHandle 是一个扩展以提供更自定义的命令式调用 API。以下三种选择是等价的:

forwardRef 仅:

const App = () => {
  const compRef = React.useRef();
  return (
    <div>
      <Comp ref={compRef} />
      <button
        onClick={() => {
          compRef.current.focus();
        }}
      >
        Focus input
      </button>
    </div>
  );
}

const Comp = React.forwardRef((props, ref) => {
  const inputRef = React.useRef();
  return <input ref={ref} />;
});

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

仅限 useImperativeHandle,使用自定义 ref 道具(查看 了解更多信息):

const App = () => {
  const compRef = React.useRef();
  return (
    <div>
      <Comp customRef={compRef} />
      <button
        onClick={() => {
          compRef.current.focus();
        }}
      >
        Focus input
      </button>
    </div>
  );
}

const Comp = ({ customRef }) => {
  const inputRef = React.useRef();
  React.useImperativeHandle(customRef, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} />;
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

useImperativeHandle + forwardRef:

const App = () => {
  const compRef = React.useRef();
  return (
    <div>
      <Comp ref={compRef} />
      <button
        onClick={() => {
          compRef.current.focus();
        }}
      >
        Focus input
      </button>
    </div>
  );
}

const Comp = React.forwardRef((props, ref) => {
  const inputRef = React.useRef();
  React.useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} />;
});

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>