简介:
大家都知道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
赋值给input
的ref
最后,通过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