我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

本文描述如何利用react功能异位渲染一个组件,和往常所不同的是:本文所渲染出来的组件并不在代码直观所显示的那个位置。而是利用传送门功能将组件位移到另外一个目标位置。但是,逻辑处理上还依然符合传统的react的组件的逻辑。就是说,组件逻辑依然保持不变,但是实际渲染的目标组件位置发生了变化,脱离了原有的体系。

苏南大叔:react教程,类组件如何利用传统门功能异位渲染? - 传送门组件
react教程,类组件如何利用传统门功能异位渲染?(图6-1)

苏南大叔的程序如此灵动博客,记录苏南大叔和计算机代码的故事。测试环境:create-react-app@5.0.1react@18.2.0react-dom@18.2.0node@16.14.2

使用传送门的原因

为什么要把渲染的目标给传送走呢?主要的原因是由于目标需要脱离原本的文档流,比如:遮罩层、模态框、提示框、消息栏、顶层浮层等。这些组件的实现原理上,基本上都是position:absolute的,然后配合lefttop之类的控制位置。

但是position:absolute有个致命问题就是,非常怕position:relative的父元素。只要父元素中出现了position:relative,这个position:absolute就可以被宣布被捕获了,无法实现既定的目标定位效果。

苏南大叔:react教程,类组件如何利用传统门功能异位渲染? - 传送门存在的原因
react教程,类组件如何利用传统门功能异位渲染?(图6-2)

基于上述理由,需要对这种组件特殊处理,不能渲染到react现有的组件里面,否则不可控。没人能确定会不会有position:relative等着捕获它。所以,需要把这些组件尽可能的直接放在body标签下,以排除其它干扰。

当然,也许有其它合理理由,让你有需求渲染一个脱离文档流的组件。不限于上述目的。

准备工作

如果有需求,那么可以:
1、为浮层准备position:absolutecss。(当然,不准备也可以)
2、为浮层准备个位于body下的#modal-rootdiv容器。(当然,不准备也可以)

目标方案就是把特殊的图层都渲染到目标容器(#modal-root)里面去。传送门函数是这么定义的:

createPortal(<组件/>,目标dom);

官方文档:https://zh-hans.reactjs.org/docs/portals.html

苏南大叔:react教程,类组件如何利用传统门功能异位渲染? - 官方文档
react教程,类组件如何利用传统门功能异位渲染?(图6-3)

实际操作上就很有想象力了,

  • <组件/>,组件可以套别的组件,还可以根据传递的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';
ReactDOMPortalReactDOM都仅仅是个名字,可以随便写的。

苏南大叔:react教程,类组件如何利用传统门功能异位渲染? - 引用对比
react教程,类组件如何利用传统门功能异位渲染?(图6-4)

测试代码一

下面的这个代码,就是模拟生成一个浮层(对话框)。

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>
  )
}

临时容器就生成在了根目录下面。

苏南大叔:react教程,类组件如何利用传统门功能异位渲染? - 渲染结果一
react教程,类组件如何利用传统门功能异位渲染?(图6-5)

测试代码二

这个代码是根据官方例子改编的:

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

苏南大叔:react教程,类组件如何利用传统门功能异位渲染? - 渲染结果二
react教程,类组件如何利用传统门功能异位渲染?(图6-6)

结束语

本文的传送门组件的写法是基于类组件的,那么现在风头正劲的函数组件呢?请听下文分解。

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   react