React挂钩和功能组件参考

时间:2019-10-16 11:01:57

标签: reactjs react-hooks react-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引用之间有什么区别?
  2. 为什么必须将forwardRef与useImperativeHandle一起使用才能将引用实际获取到Comp1?

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

3 个答案:

答案 0 :(得分:2)

反应文档说:

  

您不能在功能组件上使用ref属性,因为它们没有实例。 (more

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

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

如果要将引用与组件呈现的某些HTML元素或类组件绑定,则必须对功能组件使用forwardRef。或者,您可以使用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)?这是一个好习惯吗?

答案:使用useImperativeHandle与通过类组件中的引用调用子组件方法的不良做法相同。 React doc表示您应避免通过引用调用子组件方法,应避免使用useImperativeHandle。另外,您需要避免使用ref而不使用它们的情况。

答案 1 :(得分:1)

我将尝试回答问题2

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

  • forwardRef有助于通过一个高阶组件转发参考以引用内部DOM节点。

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

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

答案 2 :(得分:0)

1。 Comp1和Comp2引用之间有什么区别?

Comp1使用React.forwardRef,将能够从父级接收给定的ref
Comp2无法正常工作并触发以下错误:

警告:不能为功能组件提供引用。尝试访问此引用将失败。您是要使用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 仍可以传递代表ref给定的Parent的东西(如div DOM节点)。 ClassComp会在此处传递其实例。


2。为什么必须将forwardRefuseImperativeHandle一起使用才能真正获得对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,带有自定义参考道具(有关更多信息,请查看this answer):

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>