在Vue
和React
中,ref
都是框架(React
是JavaScript
库)提供给我们的一种直接 *** 作DOM
行为的方式,在Vue
中的template
模板中,不管是组件(会将外部传递的ref
属性再传递给组件内部模板定义的根元素上)还是普通标签元素(如div
、button
),使用ref
都能够引用到DOM
元素
但是在React
中,普通的标签能够赋予ref
直接引用,而对组件需要使用refs 转发
普通标签元素用法如下:
// Home.js
export default class Home extends React.Component {
state = {
buttonRef: React.createRef(),
}
componentDidMount () {
console.log(this.state.buttonRef) // { current: button }
// ref 指向了 button 元素
}
render () {
return (
<div>
<h1>Home</h1>
<button
ref={this.state.buttonRef}
>Click Me!</button>
</div>
)
}
}
过程解释:
初始化使用React.createRef()
创建了一个ref
引用实例buttonRef
,放在state
中;将this.state.buttonRef
通过ref
属性直接传递给
元素;当组件挂载完成之后,就可以通过ref.current
引用到对应的DOM
元素了。
如果将上面的部分封装成组件使用,同时赋予
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) // { current: undefined}
// ref 指向了 undefined
}
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) // { current: button }
// ref 指向了 button 元素
}
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) // { current: LogMounted }
// ref.current 指向了 LogMounted 组件
}
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
就能够引用到
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} />)
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)