React documentation says:
You may not use the ref attribute on function components because they don’t have instances. (more)
This means that you can't bind your refs to functional components. That's why your ref2.current is null. If you want to bind ref to component, you need to use class components. Your ref1 is not a ref to the Comp1 component. It actually contains an object that you passed in useImperativeHandle hook. i.e. it contains the next object:
{
print: () => {
console.log('comp1')
}
}
You have to use forwardRef with functional components if you want to bind your ref with some HTML element or class component that your component renders. Or you could bind your ref with some object with using of useImperativeHandle hook.
UPDATE
The using of useImperativeHandle is the same as adding methods to your class component:
class Comp1 extends React.Component {
print() {
console.log('comp1');
}
render() {
return (<div>comp1</div>)
}
}
is the same as
const Comp1 = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
print: () => {
console.log('comp1')
}
}), []);
return <div>comp1</div>
});
You asked in a comment:
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?
Answer: Using of useImperativeHandle is the same bad practice as calling child component methods by refs in class components. React doc says that you should avoid calling child component methods by refs, you should avoid using of useImperativeHandle. Also, you need to avoid using refs where you can do things without them.