React 支持一个特殊的、可以附加到任何组件上的 ref
属性。
此属性可以是一个对象(React 16.3)、或者一个回调函数、或者一个字符串(遗留 API)。
interface RefObject<T> {
readonly current: T | null;
}
type RefCallback<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"];
type LegacyRef<T> = string | RefCallback<T> | RefObject<T> | null;
在典型的 React
数据流中,props
是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props
来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。那么就可以通过 ref
获取到子组件的一个实例,或者一个dom
元素。
字符串
用于类组件中,例如设置了ref="inputRef"
,可以通过 this.refs.inputRef
获取到 DOM
节点/React
实例
class StringRef extends React.Component {
render() {
return <>
<input ref="inputRef" />
<Button ref="btnRef" onClick={() => {
this.refs.inputRef.value = "Click"
}}>Click</Button>
</>
}
componentDidMount() {
console.log(this.refs)
}
}
注意:不能在函数组件内使用
string
类型的ref
。
Function components cannot have string refs. We recommend using useRef() instead.
回调函数
函数中接受 React
组件实例或 HTML DOM
元素作为参数,以使它们能在其他地方被存储和访问。
React
将在组件挂载时,会调用 ref
回调函数并传入 DOM
元素,当卸载时调用它并传入 null
。在 omponentDidMount
或 componentDidUpdate
触发前,React
会保证 refs
一定是最新的。
class CallbackRef extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// 使用原生 DOM API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus();
};
}
render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
Object类型
使用 React.createRef()
创建的,并通过 ref
属性附加到 React
元素。可以通过实例的current
属性对DOM
节点或者组件实例进行访问。
class ObjectRef extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
}
focusTextInput = () => {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点
this.textInput.current.focus();
}
render() {
// 告诉 React 我们想把 <input> ref 关联到
// 构造器里创建的 `textInput` 上
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
注意:默认情况下,不能在函数组件上使用
ref
属性,因为函数组件没有实例。
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()
在函数组件上使用Ref
forwardRef,它可以将 ref
自动地通过组件传递到子组件。作为子组件第二个参数。
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
搭配 useImperativeHandle
一起使用,暴露给调用者只能使用的方法
useImperativeHandle(ref, () => ({
validate: () => {
return new Promise((resolve, reject) => {
field.validate((errors, values: any) => {
if (errors) return reject(errors);
// TODO
return resolve(values$);
});
});
}
}), []);
createRef 和 useRef 区别
createRef
常用于类组件中,useRef
只能用于函数组件useRef
返回一个可变的ref
对象,其.current
属性被初始化为传入的参数(initialValue
)。返回的对象将在组件的整个生命周期内持续存在。始终是同一个对象。
function useRef<T>(initialValue: T|null): RefObject<T>;
function useRef<T = undefined>(): MutableRefObject<T | undefined>;
// 保存上一次渲染时的状态内容
function usePrevious<T>(state: T, compare?: compareFunction<T>): T | undefined {
const prevRef = useRef<T>();
const curRef = useRef<T>();
const needUpdate = typeof compare === 'function' ? compare(curRef.current, state) : true;
if (needUpdate) {
prevRef.current = curRef.current;
curRef.current = state;
}
return prevRef.current;
}
可以发现 useRef
的用途比 ref
更加广泛,它可以存储任意的 JavaScript
值而不仅仅是 DOM
引用