React Hooks: useRef, useImperativeHandle, forwardRef的使用方法

简介:

大家都知道React中的ref属性可以帮助我们获取子组件的实例或者Dom对象,进而对子组件进行修改,是一个很方便的特性。在传统类组件中,我们通过使用 React.createRef() 创建的,并通过 ref属性附加到 React 元素来使用。而随着hooks的越来越广泛的使用,我们有必要了解一下在函数式组件中,如何使用Ref.
想要在函数式组件中使用Ref,我们必须先了解两个Api,useRef和forwardRef

1. 三者用处

1.useRef: 用于获取元素的原生DOM或者获取自定义组件所暴露出来的ref方法(父组件可以通过ref获取子组件,并调用相对应子组件中的方法)

2.useImperativeHandle:在函数式组件中,用于定义暴露给父组件的ref方法。

3.React.forwardRef:ref父类的ref作为参数传入函数式组件中,本身props只带有children这个参数,这样可以让子类转发父类的ref,当父类把ref挂在到子组件上时,子组件外部通过forwrardRef包裹,可以直接将父组件创建的ref挂在到子组件的某个dom元素上

useRef
const refContainer = useRef(initialValue);

useRef返回一个可变的ref对象,其.current属性被初始化为传入的参数(initialValue)。返回的ref对象在整个生命周期内保持不变。
下面看一个例子

function TextInputWithFocusButton() {
  // 关键代码
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // 关键代码,`current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      // 关键代码
      <input ref={inputEl} type="text" />

      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

在这里插入图片描述

可以看到我们点击button,先通过useRef创建一个ref对象inputEl,然后再将inputEl赋值给inputref最后,通过inputEl.current.focus()就可以让input聚焦。
然后,我们再想下,如果input不是个普通的dom元素,而是个组件,该怎么办呢?
这就牵扯到另外一个api,forwardRef。

useImperativeHandle

有时候,我们可能不想将整个子组件暴露给父组件,而只是暴露出父组件需要的值或者方法,这样可以让代码更加明确。而useImperativeHandleApi就是帮助我们做这件事的。
语法:

 useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle可以让你在使用 ref 时自定义暴露给父组件的实例值。
一个例子🌰:

const TextInput =  forwardRef((props,ref) => {
  const inputRef = useRef();
  // 关键代码
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} />
})
function TextInputWithFocusButton() {
  // 关键代码
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // 关键代码,`current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      // 关键代码
      <TextInput ref={inputEl}></TextInput>
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

这样,我们也可以使用current.focus()来事input聚焦。这里要注意的是,子组件TextInput中的useRef对象,只是用来获取input元素的,大家不要和父组件的useRef混淆了。

React.forwardRef
function InputWithLabel(props) {
  // 这里的myRef为通过外部打入的父级ref节点
  const { label, myRef } = props;
  const [value, setValue] = useState("");
  const handleChange = e => {
    const value = e.target.value;
    setValue(value);
  };

  return (
    <div>
      <span>{label}:</span>
      <input type="text" ref={myRef} value={value} onChange={handleChange} />
    </div>
  );
}

// 这里用forwardRef来承接得到父级传入的ref节点,并将其以参数的形式传给字节点
const RefInput = React.forwardRef((props, ref) => (
  <InputWithLabel {...props} myRef={ref} />
));

// 调用该RefInput的过程
function App() {
  // 通过useRef hook 获得相应的ref节点
  const myRef = useRef(null);

  const handleFocus = () => {
    const node = myRef.current;
    console.log(node);
    node.focus();
  };

  return (
    <div className="App">
      <RefInput label={"姓名"} ref={myRef} />
      <button onClick={handleFocus}>focus</button>
    </div>
  );
}

在这里插入图片描述

结果分析:通过focus直接子元素中input的DOM节点
存在问题

这样我们的Ref获得的是整个节点,但是有时候我们通过ref只需要暴露一部分参数就行了,为了解决这个问题,我们就需要用到useImperativeHandle来指定放出一部分我们需要的方法或者属性给父级

使用forwardRef和useImperativeHandle的方案
  • 思路:
    • 子组件内部自建一个_innerRef来获取ref元素
    • 将通过forwarfRef传入的ref元素通过useImperativeHandle来进行绑定,指定该子组件对外暴露的方法或属性
    • 通过_innerRef调用响应的方法然后同时在useImperativeHandle中写代码即可,这样可以只暴露一部分方法属性,而不是整个底层的input原生DOM节点
function InputWithLabel(props) {
  const { label, myRef } = props;
  const [value, setValue] = useState("");
  const _innerRef = useRef(null);
  const handleChange = e => {
    const value = e.target.value;
    setValue(value);
  };

  const getValue = () => {
    return value;
  };

  useImperativeHandle(myRef, () => ({
    getValue,
    focus() {
      const node = _innerRef.current;
      node.focus();
    }
  }));

  return (
    <div>
      <span>{label}:</span>
      <input
        type="text"
        ref={_innerRef}
        value={value}
        onChange={handleChange}
      />
    </div>
  );
}
结果分析:通过focus只能获取我们指定向外暴露那部分的方法

完整代码

import React, { useRef, useState, useImperativeHandle } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function InputWithLabel(props) {
  const { label, myRef } = props;
  const [value, setValue] = useState("");
  const _innerRef = useRef(null);
  const handleChange = e => {
    const value = e.target.value;
    setValue(value);
  };

  const getValue = () => {
    return value;
  };

  useImperativeHandle(myRef, () => ({
    getValue,
    focus() {
      const node = _innerRef.current;
      node.focus();
    }
  }));

  return (
    <div>
      <span>{label}:</span>
      <input
        type="text"
        ref={_innerRef}
        value={value}
        onChange={handleChange}
      />
    </div>
  );
}

const RefInput = React.forwardRef((props, ref) => (
  <InputWithLabel {...props} myRef={ref} />
));

function App() {
  const myRef = useRef(null);

  const handleFocus = () => {
    const node = myRef.current;
    console.log(node);
    node.focus();
  };

  return (
    <div className="App">
      <RefInput label={"姓名"} ref={myRef} />
      <button onClick={handleFocus}>focus</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

觉得这两篇文章写的挺好的,就拿过来了

原文:月半叫做胖:
https://blog.csdn.net/qq_24724109/article/details/103817607?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.essearch_pc_relevant&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.essearch_pc_relevant

北辰_狼月:https://www.jianshu.com/p/11198c03a038

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值