react教程,类组件如何利用传统门功能异位渲染?
发布于 作者:苏南大叔 来源:程序如此灵动~本文描述如何利用react
功能异位渲染一个组件,和往常所不同的是:本文所渲染出来的组件并不在代码直观所显示的那个位置。而是利用传送门功能将组件位移到另外一个目标位置。但是,逻辑处理上还依然符合传统的react
的组件的逻辑。就是说,组件逻辑依然保持不变,但是实际渲染的目标组件位置发生了变化,脱离了原有的体系。
苏南大叔的程序如此灵动博客,记录苏南大叔和计算机代码的故事。测试环境:create-react-app@5.0.1
,react@18.2.0
,react-dom@18.2.0
,node@16.14.2
。
使用传送门的原因
为什么要把渲染的目标给传送走呢?主要的原因是由于目标需要脱离原本的文档流,比如:遮罩层、模态框、提示框、消息栏、顶层浮层等。这些组件的实现原理上,基本上都是position:absolute
的,然后配合left
、top
之类的控制位置。
但是position:absolute
有个致命问题就是,非常怕position:relative
的父元素。只要父元素中出现了position:relative
,这个position:absolute
就可以被宣布被捕获了,无法实现既定的目标定位效果。
基于上述理由,需要对这种组件特殊处理,不能渲染到react
现有的组件里面,否则不可控。没人能确定会不会有position:relative
等着捕获它。所以,需要把这些组件尽可能的直接放在body
标签下,以排除其它干扰。
当然,也许有其它合理理由,让你有需求渲染一个脱离文档流的组件。不限于上述目的。
准备工作
如果有需求,那么可以:
1、为浮层准备position:absolute
的css
。(当然,不准备也可以)
2、为浮层准备个位于body
下的#modal-root
的div
容器。(当然,不准备也可以)
目标方案就是把特殊的图层都渲染到目标容器(#modal-root
)里面去。传送门函数是这么定义的:
createPortal(<组件/>,目标dom);
官方文档:https://zh-hans.reactjs.org/docs/portals.html
实际操作上就很有想象力了,
<组件/>
,组件可以套别的组件,还可以根据传递的props.children
显示其它受控内容。目标dom
,渲染到根目录下面的临时div
容器里面,还是渲染到专门的div
的临时div
里面?不管是哪里,记得卸载的时候删除这个临时div
。
引入传送门
传送门函数是createPortal()
,它存在的位置是react-dom
。注意并不是平时大家写的react-dom/client
,也不是react-dom/server
,就是非常单纯的react-dom
。
所以,引入方式有两种写法:
import ReactDOMPortal from 'react-dom';
// ReactDOMPortal.createPortal()
或者
import { createPortal } from 'react-dom';
// createPortal()
对比一下大家平时使用频率最高的引入语句,就可以看出区别。
import ReactDOM from 'react-dom/client';
import ReactDOMPortal from 'react-dom';
ReactDOMPortal
、ReactDOM
都仅仅是个名字,可以随便写的。
测试代码一
下面的这个代码,就是模拟生成一个浮层(对话框)。
import React from 'react';
import { createPortal } from 'react-dom';
class Dialog extends React.Component {
constructor(props) {
super(props);
this.node = window.document.createElement('div');
window.document.body.appendChild(this.node);
}
render() {
return createPortal(
<div className="dialog">
{this.props.children}
</div>,
this.node
);
}
componentWillUnmount() {
window.document.body.removeChild(this.node);
}
}
export default function App() {
return (
<Dialog>
内置html
</Dialog>
)
}
临时容器就生成在了根目录下面。
测试代码二
这个代码是根据官方例子改编的:
import React from 'react';
import ReactDOMPortal from 'react-dom';
const root2 = document.getElementById('root-modal');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
root2.appendChild(this.el);
}
componentWillUnmount() {
root2.removeChild(this.el);
}
render() {
return ReactDOMPortal.createPortal(
this.props.children,
this.el
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { clicks: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
clicks: state.clicks + 1
}));
}
render() {
return (
<div onClick={this.handleClick}>
<Modal>
<Child cnt={this.state.clicks} />
</Modal>
</div>
);
}
}
function Child(props) {
return (
<button>实际脱离文档流了,但是依然响应react事件[{props.cnt}]</button>
);
}
export default App;
1、这个代码中,需要在index.html
的传统root
节点旁边,放过root-modal
节点作为容器。
2、注意看这里的click
事件,对于异位到另外一个容器的按钮,仍然生效。(并没有用props
来传递click
事件)
3、Modal
组件就是具有传送门功能的组件,控制其内容的方式是定义props.children
。
结束语
本文的传送门组件的写法是基于类组件的,那么现在风头正劲的函数组件呢?请听下文分解。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。