5
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 参考有什么区别?
  2. 为什么我必须使用 forwardRef 和 useImperativeHandle 才能真正获得 Comp1 的引用?

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

4

3 回答 3

6

反应文档说:

你不能在函数组件上使用 ref 属性,因为它们没有实例。(更多

这意味着您不能将 refs 绑定到功能组件。这就是你的ref2.current原因null。如果要将 ref 绑定到组件,则需要使用类组件。您ref1不是Comp1组件的引用。它实际上包含一个您在useImperativeHandle钩子中传递的对象。即它包含下一个对象:

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

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

更新

的使用与useImperativeHandle向您的类组件添加方法相同:

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>
});

你在评论中问:

所以在转向钩子(并且仍然避免使用类)之后,使用 ref 的唯一方法是使用useImperativeHandle(并且实际上使用“假” ref)?这是一个好习惯吗?

答:使用 ofuseImperativeHandle与在类组件中通过 refs 调用子组件方法是一样的坏习惯。React 文档说你应该避免通过 refs 调用子组件方法,你应该避免使用useImperativeHandle. 此外,您需要避免在没有它们的情况下使用 refs。

于 2019-10-16T11:33:36.353 回答
3

1. Comp1 和 Comp2 refs 有什么区别?

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

警告:不能给函数组件提供参考。尝试访问此 ref 将失败。你的意思是使用React.forwardRef()

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

问题 - 没有功能组件的实例
// 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 仍然可以将代表的东西传递ref给给定的 from Parent(如DOM 节点)。而是在这里传递它的实例。divClassComp


2. 为什么我必须使用forwardRefwithuseImperativeHandle才能真正获得 ref 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>

于 2020-06-05T10:09:49.480 回答
1

我将尝试从问题 2 中回答

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

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

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

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

于 2019-10-16T11:27:48.743 回答