众所周知,React 通过声明式的渲染机制把复杂的 DOM 操作抽象成为简单的 state 与 props 操作,一时圈粉无数,一夜间将前端工程师从面条式的 DOM 操作中拯救出来。尽管我们一再强调在 React 开发中尽量避免 DOM 操作,但在一些场景中仍然无法避免。当然 React 并没有把路堵死,它提供了 ref 用于访问在 render 方法中创建的 DOM 元素或者是 React 组件实例。
在 React v16.3 之前,ref 通过字符串(string ref)或者回调函数(callback ref)的形式进行获取,在 v16.3 中,经 0017-new-create-ref 提案引入了新的 React.createRef API。
在 React.createRef 出现之前,string ref 就已被诟病已久,React 官方文档直接提出 string ref 将会在未来版本被移出,建议用户使用 callback ref 来代替,为何需要这么做呢?主要原因集中于以下几点:
- 当 ref 定义为 string 时,需要 React 追踪当前正在渲染的组件,在 reconciliation 阶段,React Element 创建和更新的过程中,ref 会被封装为一个闭包函数,等待 commit 阶段被执行,这会对 React 的性能产生一些影响。
- 当使用 render callback 模式时,使用 string ref 会造成 ref 挂载位置产生歧义。
- string ref 无法被组合,例如一个第三方库的父组件已经给子组件传递了 ref,那么我们就无法再在子组件上添加 ref 了,而 callback ref 可完美解决此问题。
- 在根组件上使用无法生效。
- 对于静态类型较不友好,当使用 string ref 时,必须显式声明 refs 的类型,无法完成自动推导。
- 编译器无法将 string ref 与其 refs 上对应的属性进行混淆,而使用 callback ref,可被混淆。
详细参考:React ref 的前世今生
1、父组件操作子组件DOM
1.1、函数组件——React.forwardRef
React16 提供了一个关于 ref 的 API React.forwardRef,主要用于穿过父元素直接获取子元素的 ref,起到一个中转ref的作用,无需由我们自己来实现一个HOC
function forwardRef<T, P = {}>(render: ForwardRefRenderFunction<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
interface ForwardRefRenderFunction<T, P = {}> {
(props: PropsWithChildren<P>, ref: ForwardedRef<T>): ReactElement | null;
displayName?: string;
// explicit rejected with `never` required due to
// https://github.com/microsoft/TypeScript/issues/36826
/**
* defaultProps are not supported on render functions
*/
defaultProps?: never;
/**
* propTypes are not supported on render functions
*/
propTypes?: never;
}
ref不能通过props传递,React.forwardRef的出现使之成为可能。React.forwardRef是一个HOC,接收一个函数组件,使这个函数组件接收ref。
这里需要注意,泛型类型T指ref的类型,P指props类型。
// 父组件
const Father = () => {
const ref = React.createRef<HTMLInputElement>();
useEffect(() => {
if (ref.current) {
ref.current.focus();
}
})
return <Child ref={ref}></Child>
}
// 子组件
const Child = React.forwardRef<HTMLInputElement, any>((props, ref) => {
return <input type="text" ref={ref}>child</input>
})
1.2、类组件——React.createRef
// 父组件
class Father extends React.Component<{}, {}> {
private inpurRef = React.createRef<HTMLInputElement>();
componentDidMount() {
this.inpurRef.current?.focus();
}
render() {
return <Child inputRef={this.inpurRef}></Child>
}
}
export default Father;
// 子组件
interface IProps {
inputRef: React.RefObject<HTMLInputElement>
}
class Child extends React.Component<IProps, {}> {
render() {
const { inputRef } = this.props;
return <input type="text" ref={inputRef}/>
}
}
export default Child;
1.3、类组件——callback ref
ref 通过回调函数(callback ref)的形式进行获取
// 父组件
class Father extends React.Component<{}, {}> {
private inputRef: HTMLInputElement | null = null;
generateRef = (el: HTMLInputElement) => {
this.inputRef = el;
}
componentDidMount() {
if (this.inputRef) {
this.inputRef.focus();
}
}
render() {
return <Child generateRef={this.generateRef}></Child>
}
}
export default Father;
// 子组件
interface IProps {
generateRef: (el: HTMLInputElement) => void;
}
class Child extends React.Component<IProps, {}> {
render() {
const { generateRef } = this.props;
return <input type="text" ref={generateRef}/>
}
}
export default Child;
2、组件内操作DOM
2.1、函数组件——React.createRef
// React.RefObject<HTMLInputElement>
import React, { useEffect } from 'react';
const InputFocus = () => {
const inputRef = React.createRef<HTMLInputElement>();
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
})
return <input type="text" ref={inputRef}/>
}
export default InputFocus;
2.2、函数组件——callback ref
// ((instance: HTMLInputElement | null) => void)
import React, { useEffect } from 'react';
const InputFocus = () => {
let inputRef: HTMLDivElement | null = null;
useEffect(() => {
if (inputRef) {
inputRef.focus();
}
})
return <input type="text" ref={(el) => inputRef = el}/>
}
export default InputFocus;
2.3、类组件——React.createRef
// React.RefObject<HTMLInputElement>
class InputFocus extends React.Component<{}, {}> {
private inputRef = React.createRef<HTMLInputElement>();
componentDidMount(){
if (this.inputRef.current) {
this.inputRef.current.focus()
}
}
render() {
return <input type="text" ref={this.inputRef}/>
}
}
export default InputFocus;
2.4、类组件——callback ref
// ((instance: HTMLInputElement | null) => void)
class InputFocus extends React.Component<{}, {}> {
private inputRef: HTMLInputElement | null = null;
componentDidMount(){
if (this.inputRef) {
this.inputRef.focus()
}
}
render() {
return <input type="text" ref={(el) => this.inputRef = el}/>
}
}
export default InputFocus;
3、使用ref获取组件实例
使用ref获取组件实例只能用在类组件上,因为函数组件没有实例
// 父组件
const Father = () => {
const ref = useRef<Child | null>(null);
useEffect(() => {
if (ref.current) {
ref.current.inputFocus();
}
})
return <Child ref={ref}></Child>
}
export default Father;
// 子组件
class Child extends React.Component<{}, {}> {
private inputRef = React.createRef<HTMLInputElement>();
inputFocus = () => {
if(this.inputRef.current) {
this.inputRef.current.focus();
}
}
render() {
return <input type="text" ref={this.inputRef} onFocus={this.inputFocus}></input>
}
}
export default Child;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。