React Refs 使用与转发
Refs(Resilient File System)即弹性文件系统。
React 的 Refs 提供了一种方式,允许访问 DOM 节点或 render 方法中创建的 React 元素。
根据 React refs 用法,可以分为两大类:
不跨层级调用节点或实例
例如单个组件内部调用其 return 的节点或实例:
const App = () => {
const theDiv = useRef(null)
useEffect(() => {
console.log(theDiv.current) // <div>Hello World</div>
})
return <div ref={theDiv}>Hello World</div>
}跨层级调用节点或实例
例如父组件调用子组件中的某个节点或实例:
const LnButton = (props) => {
return <button ref={props.btnRef}>{props.children}</button>
}
const App = () => {
const lnBtn = useRef(null)
useEffect(() => {
console.log(lnBtn.current) // <button>按钮</button>
})
return <LnButton btnRef={lnBtn}>按钮</LnButton>
}
一、不跨层级调用节点或实例
注意:原有的字符串式
this.refs.xxx
的用法已被废弃,不再使用。
1、React.createRef() 用法
使用 React.createRef()
创建 Refs,并通过 ref 属性附加到 React 元素中。
const MyComponent = () => {
const myRef = React.createRef()
return <div ref={myRef} />
}
当 ref 被传递给 return 的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。
const node = myRef.current
ref 的值根据节点的类型而有所不同:
- 当 ref 属性用于 HTML 元素时,构造函数中使用
React.createRef()
创建的 ref 接收底层 DOM 元素作为其 current 属性; - 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性;
- 不能在函数组件上使用 ref 属性,因为他们没有实例。但可以在函数组件中使用 ref。
2、回调 refs 用法
回调 refs 也是 React 设置 refs 的方式,可以更精细地控制何时 refs 被设置和解除(只能用于 Class 组件)
回调 refs 传递一个函数,在函数中接受 React 组件实例或 HTML DOM 元素作为参数,使它们能在其他地方被存储和访问。举个例子:
class App extends React.Component {
constructor(props) {
super(props)
this.setTheDivRef = (node) => {
this.theDiv = node
}
}
componentDidMount() {
console.log(this.theDiv) // <div>Hello World</div>
}
render() {
return <div ref={this.setTheDivRef}>Hello World</div>
}
}
ref 回调函数也可以用内联函数的方式定义,例如:
class App extends React.Component {
componentDidMount() {
console.log(this.theDiv) // <div>Hello World</div>
}
render() {
return (
<div
ref={(node) => {
this.theDiv = node
}}
>
Hello World
</div>
)
}
}
用内联函数的方式定义回调 Refs,在更新过程中它会被执行两次,第一次传入参数 null
,然后第二次再传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 会清空旧的 ref 并设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题。
3、useRef() 用法
useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
import React, { useRef, useEffect } from 'react'
const App = () => {
const theDiv = useRef(null)
useEffect(() => {
console.log(theDiv.current) // <div>Hello World</div>
})
return <div ref={theDiv}>Hello World</div>
}
注意:当 ref 对象内容发生变化时,useRef 不会通知你。变更 current 属性不会引发组件重新渲染。如果想在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,需要使用回调 ref 来实现。
二、跨层级调用节点或实例
1、refs 转发
当需要在父组件中操作子组件的 DOM 节点时,可以使用 refs 转发。refs 转发可以对父组件暴露子组件的 DOM 节点,主要通过 forwardRef()
来进行转发。
使用前:
const LnButton = (props) => {
return <button>{props.children}</button>
}
const App = () => {
return <LnButton>按钮</LnButton>
}
现在需要在 App(父组件)中获取 LnButton(子组件)中 button 的 ref:
import React, { useEffect } from 'react'
const LnButton = React.forwardRef((props, curRef) => {
return <button ref={curRef}>{props.children}</button>
})
const App = () => {
const lnBtn = React.createRef()
useEffect(() => {
console.log(lnBtn.current) // <button>按钮</button>
})
return <LnButton ref={lnBtn}>按钮</LnButton>
}
export default App
不建议使用 forwardRef 时,因为可能出现 refs 的分配及导出类型等问题。
如果使用 16.2 或更低版本的 React,或想要比 ref 转发更高的灵活性,可以将 ref 作为组件属性向下传递到目标节点或实例上,例如 ▼
2、传递 createRef
通过 createRef 的方式将 ref 作为组件属性向下传递到目标节点,从而在父组件中拿到子组件的节点:
const LnButton = (props) => {
return <button ref={props.btnRef}>{props.children}</button>
}
const App = () => {
const lnBtn = React.createRef()
useEffect(() => {
console.log(lnBtn.current) // <button>按钮</button>
})
return <LnButton btnRef={lnBtn}>按钮</LnButton>
}
3、传递回调 refs
通过回调 refs 的方式将 ref 作为组件属性向下传递到目标节点,从而在父组件中拿到子组件的节点:
const LnButton = (props) => {
return (
<button
ref={(node) => {
props.btnRef(node)
}}
>
{props.children}
</button>
)
}
class App extends React.Component {
componentDidMount() {
console.log(this.lnBtn) // <button>按钮</button>
}
btnRefHandler = (node) => {
this.lnBtn = node
}
render() {
return <LnButton btnRef={this.btnRefHandler}>按钮</LnButton>
}
}
4、传递 useRef
通过 useRef 的方式将 ref 作为组件属性向下传递到目标节点,从而在父组件中拿到子组件的节点:
const LnButton = (props) => {
return <button ref={props.btnRef}>{props.children}</button>
}
const App = () => {
const lnBtn = useRef(null)
useEffect(() => {
console.log(lnBtn.current) // <button>按钮</button>
})
return <LnButton btnRef={lnBtn}>按钮</LnButton>
}