一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情。
前言
大家好呀,我是L同学。在上篇文章中react基础(十二)— 高阶组件的使用我们学习了react中高阶组件的知识点。今天我们学习下forwardRef的使用和Portals的使用。
forwardRef的使用
在这篇文章react基础(十一)中,我们可以通过创建ref来获取对应的DOM。使用ref推荐的方式是传入一个对象,通过React.reactRef()方式创建一个对象,使用时获取到创建的对象其中有一个current属性就是对应的元素。我们来复习下。
import React, { PureComponent, createRef } from 'react'
export default class App extends PureComponent {
constructor(props) {
super(props)
this.titleRef = createRef()
}
render() {
return (
<div>
<h2 ref={this.titleRef}>hello world</h2>
<button onClick={e => this.printRef()}>打印ref</button>
</div>
)
}
printRef() {
console.log(this.titleRef.current);
}
}
点击打印ref按钮可以获取到DOM元素h2。
除了获取DOM元素,还可以通过ref获取组件。
创建一个Home组件。
class Home extends PureComponent {
render() {
return <h2>Home</h2>
}
}
export default class App extends PureComponent {
constructor(props) {
super(props)
this.homeRef = createRef()
}
render() {
return (
<div>
<Home ref={this.homeRef}/>
<button onClick={e => this.printRef()}>打印ref</button>
</div>
)
}
printRef() {
console.log(this.homeRef.current);
}
}
可以看到点击打印ref按钮获取到了Home组件。
在讲解ref的那篇文章中,我们说过ref不能应用于函数式组件。因为函数式组件没有实例,所以不能获取到对应的组件对象。
我们来尝试下。
首先我们创建一个函数式组件Profile。
function Profile() {
return <p>Profile</p>
}
跟上述操作一致,通过createRef()创建profileRef对象,然后将这个对象传入Profile组件中的ref属性。
<Profile ref={this.profileRef}/>
此时,页面控制台报了一个警告。
并且点击按钮,无法获取到Profile组件。
我想要拿到Profile组件的p元素,那么有人会这样想,可以把ref作为属性,传递给子组件,然后子组件通过props能获取到吗?就像传递name属性给子组件Profile。
<Profile ref={this.profileRef} name={'haha'}/>
然后在子组件Profile中通过props进行接收。
function Profile (props) {
console.log(props.ref);
return <p ref={props.ref}>Profile</p>
}
我们可以看到一个警告,提示ref不是一个prop传递给下一个组件,这个ref是react内部进行管理的。
在警告提示中,我们可以看到可以通过forwardRef高阶组件来获取函数式组件中某个元素的DOM。
接下来我们通过forwardRef高阶组件来拿到Profile组件中的p元素。
在上一篇文章中,我们学习了高阶组件的定义和使用。高阶组件是一个函数,接收一个组件作为参数,并且返回一个组件。
const Profile = forwardRef(function(props, ref) {
return <p ref={ref}>Profile</p>
})
我们可以看到点击打印ref按钮能获取到Profile组件中的p元素。
Portals的使用
在某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的MON元素上)。
举个例子。在Home组件中有个按钮,我希望点击按钮,就能在页面中间弹出个弹窗。我们不能在Home组件中渲染弹窗,因为弹窗很大。
import React, {PureComponent} from 'react'
class Home extends PureComponent {
render() {
return (
<div>
<h2>Home</h2>
</div>
)
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Home/>
</div>
)
}
}
这样的需求我们可以使用Portals。Portal提供了一种将子节点渲染到存在于父组件之外的DOM节点的方法。
第一个参数child是任何可渲染的react子元素。第二个参数container是一个DOM元素。
ReactDOM.createPortal(child, container)
比如说,我们开发一个Model组件,将它的子组件渲染到屏幕中间。
首先我们在index.html中创建一个id为model的元素,用来挂载。
<div id="model"></div>
编写这个节点的样式。
#model {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
编写组件代码。
import ReactDOM from 'react-dom'
class Model extends PureComponent {
render() {
return ReactDOM.createPortal(
this.props.children,
document.getElementById('model')
)
}
}
class Home extends PureComponent {
render() {
return (
<div>
<h2>Home</h2>
<Model>
<h2>Title</h2>
</Model>
</div>
)
}
}