基本使用
在Vue 和React 中,ref 都是框架(React 是JavaScript 库)提供给我们的一种直接操作DOM 行为的方式,在Vue 中的template 模板中,不管是组件(会将外部传递的ref 属性再传递给组件内部模板定义的根元素上)还是普通标签元素(如div 、button ),使用ref 都能够引用到DOM 元素
但是在React 中,普通的标签能够赋予ref 直接引用,而对组件需要使用refs 转发
普通标签元素用法如下:
export default class Home extends React.Component {
state = {
buttonRef: React.createRef(),
}
componentDidMount () {
console.log(this.state.buttonRef)
}
render () {
return (
<div>
<h1>Home</h1>
<button
ref={this.state.buttonRef}
>Click Me!</button>
</div>
)
}
}
过程解释:
- 初始化使用
React.createRef() 创建了一个ref 引用实例buttonRef ,放在state 中; - 将
this.state.buttonRef 通过ref 属性直接传递给<button /> 元素; - 当组件挂载完成之后,就可以通过
ref.current 引用到对应的DOM 元素了。
如果将上面的<button /> 部分封装成组件使用,同时赋予ref 属性,代码如下:
function InnerButton (props) {
console.log('props: ', props)
return (
<button
ref={props.ref}
>
{props.children}
</button>
)
}
export default class Home extends React.Component {
state = {
buttonRef: React.createRef(),
}
componentDidMount () {
console.log(this.state.buttonRef)
}
render () {
return (
<div>
<h1>Home</h1>
<InnerButton ref={this.state.buttonRef} />
</div>
)
}
}
在InnerButton 内部打印了props 输出结果如下
可见,ref 虽然在props 中被传递过来了,但是它的值却为undefined ,而且在componentDidMount 钩子函数中打印的buttonRef.current 同样指向undefined ,警告提示:ref is not a prop.
其原因是,React 将ref 属性做了特殊处理,当ref 需要引用组件时,必须使用React.forwardRef() 方法二次包装
代码修改如下:
const InnerButton = React.forwardRef(function (props, ref) {
return (
<button
ref={ref}
>
{props.children}
</button>
)
})
export default class Home extends React.Component {
state = {
buttonRef: React.createRef(),
}
componentDidMount () {
console.log(this.state.buttonRef)
}
render () {
return (
<div>
<h1>Home</h1>
<InnerButton ref={this.state.buttonRef} />
</div>
)
}
}
HOC 使用
当需要使用ref 转发的组件被高阶组件又包装了一层时,又该如何转发呢?
先编写如下测试代码:
function logMounted (Component) {
class LogMounted extends React.Component {
componentDidMount () {
console.log(`>---- ${Component.name ? Component.name : 'Component' } has mounted ----<`)
}
render () {
return <Component {...this.props} />
}
}
return LogMounted
}
const InnerButton = React.forwardRef(function (props, ref) {
return (
<button
ref={ref}
>
{props.children}
</button>
)
})
InnerButton.name = 'InnerButton'
const LogMountedInnerButton = logMounted(InnerButton)
export default class Home extends React.Component {
state = {
buttonRef: React.createRef(),
}
componentDidMount () {
console.log(this.state.buttonRef)
}
render () {
return (
<div>
<h1>Home</h1>
<LogMountedInnerButton ref={this.state.buttonRef} />
</div>
)
}
}
函数式组件logMounted 的作用是在组件挂载完之后,打印出'xxx has mounted' 信息。通过该函数const LogMountedInnerButton = logMounted(InnerButton) 包装之前的InnerButton 返回一个新的组件,在Home JSX中同样赋予了ref 属性
而在控制台查看打印信息,this.state.buttonRef 指向的却是组件LogMounted ,原因同先前一样:ref 依然被当做了普通props 传递了
所以,要达到ref 既能作为特殊属性在高阶组件上使用,又能够有效地传递到被包装的组件内部,在函数式组件logMounted 返回的内容做处理:
- 使用
React.forwardRef() 方法对返回的组件再做一次包装; - 增加一个
forwardedRef 属性,单独用于传递ref ; - 在
LogMounted 组件内部解构,再次往下传递给被包装的组件; ref 就能够引用到<button />
function logMounted (Component) {
class LogMounted extends React.Component {
componentDidMount () {
console.log(`>---- ${Component.name ? Component.name : 'Component' } has mounted ----<`)
}
render () {
const { forwardedRef, ...rest } = this.props
return <Component {...rest} ref={forwardedRef} />
}
}
return React.forwardRef((props, ref) => <LogMounted {...props} forwardedRef={ref} />)
}
|