弹层管理器

20 阅读4分钟

最近我反复理解这句话:“react是一种UI渲染库,而不是一个框架”,探索UI与业务逻辑分离的底层实现原理;

react本身,提供的核心功能,是UI映射的方案;但是对一个完整的前端应用来说,UI只是应用的一部分,除了UI还有很多事情要处理;

1、从服务端获取数据; 2、对数据二次加工 3、处理业务逻辑:弹窗、上传、更新……

总的来说,应用 = 业务逻辑+UI;

UI只是最底层,实现数据展示和数据交互的工具;

将数据层和UI层解耦;

同一个业务,会映射多种UI; 同一个UI,会被多个业务场景复用;

为使UI能够尽量的实现复用,也为使UI足够单一和解耦,尽量将数据层抽离,使用分层思想,而react提供的hook、render、createElement等api,只作为UI的一种,桥接工具,有了这个思想,去做应用级、工程化的框架设计,就有了很大的空间,而不是限制在,将react作为一个庞大的组件来设计;

将这个思想应用在,本文的主题,弹层管理器上,便有了实现的依据

之前我的思路一直,很难脱离react框架本身的api设计思路,总是受制于其更新逻辑的限制,但是,跳出react框架本身,react,只是用于创建、更新UI的一个工具,而弹层逻辑,和其他接口请求等逻辑,并无异处;

弹层,或者其他投承的浮层,本质上,都是在基础页面之上打开/创建一个新的UI组件/面板,她们的特点是,弹层/浮层的状态自治,且只在打开后生效,关闭后消失,那么其生命周期就只有一次,既然有了这个特色,完全可以脱离react基础应用组件的生命周期,手动接管;

而且还可以异步创建,不影响主应用的页面性能;

非常好的建议!使用 React.createRoot 可以让我们更灵活地管理弹窗的渲染,特别是在需要将弹窗渲染到特定 DOM 节点的情况下。这种方法也符合 React 18 及以后版本的最佳实践。让我们来重构之前的代码,使用 React.createRoot 来渲染弹窗。 首先,我们需要修改 ModalManager 类:


import React from 'react'; 
import { createRoot, Root } from 'react-dom/client'; 
type ModalConfig = { 
    id: string; 
    component: React.ComponentType<any>; 
    props?: Record<string, any>; 
 }; 
 type ModalInstance = { id: string; resolve: (value: any) => void; reject: (reason?: any) => void; }; 
    
class ModalManager { private modalQueue: ModalConfig[] = []; private currentModal: ModalInstance | null = null; private isShowing: boolean = false; private root: Root | null = null; private container: HTMLElement | null = null; constructor() { // 创建一个容器元素 this.container = document.createElement('div'); this.container.id = 'modal-container'; document.body.appendChild(this.container); // 创建 React root this.root = createRoot(this.container); } showModal(config: ModalConfig): Promise<any> { return new Promise((resolve, reject) => { this.modalQueue.push(config); this.processQueue(); const instance: ModalInstance = { id: config.id, resolve, reject }; if (!this.currentModal) { this.currentModal = instance; } }); } private processQueue() { if (!this.isShowing && this.modalQueue.length > 0) { this.isShowing = true; this.renderModal(); } } closeCurrentModal(result?: any) { if (this.currentModal) { this.currentModal.resolve(result); this.currentModal = null; } this.modalQueue.shift(); if (this.modalQueue.length === 0) { this.isShowing = false; this.renderModal(); // 重新渲染以清除模态框 } else { this.currentModal = { id: this.modalQueue[0].id, resolve: () => {}, reject: () => {} }; this.renderModal(); // 渲染下一个模态框 } } private renderModal() { if (!this.root) return; const currentModal = this.getCurrentModal(); if (currentModal) { const ModalComponent = currentModal.component; this.root.render( <div className="modal-overlay"> <div className="modal-content"> <ModalComponent {...currentModal.props} onClose={(result: any) => this.closeCurrentModal(result)} /> </div> </div> ); } else { // 如果没有模态框要显示,则渲染空内容 this.root.render(null); } } getCurrentModal(): ModalConfig | null { return this.isShowing ? this.modalQueue[0] : null; } isModalVisible(): boolean { return this.isShowing; } // 清理方法,在不再需要模态框管理器时调用 dispose() { if (this.root) { this.root.unmount(); this.root = null; } if (this.container && this.container.parentNode) { this.container.parentNode.removeChild(this.container); this.container = null; } } } export const modalManager = new ModalManager(); 

现在,我们不再需要单独的 ModalContainer 组件,因为模态框直接通过 React.createRoot 渲染到 DOM 中。 使用这个管理器的例子保持不变:

import React from 'react'; import { modalManager } from './ModalManager'; const ConfirmModal: React.FC<{ onClose: (result: boolean) => void, message: string }> = ({ onClose, message }) => ( <div> <p>{message}</p> <button onClick={() => onClose(true)}>确认</button> <button onClick={() => onClose(false)}>取消</button> </div> ); const AlertModal: React.FC<{ onClose: () => void, message: string }> = ({ onClose, message }) => ( <div> <p>{message}</p> <button onClick={onClose}>确定</button> </div> ); const App: React.FC = () => { const handleShowModals = async () => { const result1 = await modalManager.showModal({ id: 'confirm1', component: ConfirmModal, props: { message: '您确定要继续吗?' } }); if (result1) { const result2 = await modalManager.showModal({ id: 'confirm2', component: ConfirmModal, props: { message: '真的确定吗?' } }); if (result2) { await modalManager.showModal({ id: 'alert1', component: AlertModal, props: { message: '操作已完成!' } }); } } console.log('All modals closed'); }; return ( <div> <button onClick={handleShowModals}>显示连续弹窗</button> </div> ); }; export default App; 

这种实现方式有以下优点:

  1. 更好的封装:模态框的渲染完全由 ModalManager 管理,不需要在主应用中包含额外的容器组件。

  2. 更灵活的控制:可以将模态框渲染到任何指定的 DOM 节点,而不仅限于应用的主要渲染树中。

  3. 性能优化:当没有模态框显示时,可以完全卸载模态框的渲染树,减少不必要的 DOM 操作。

  4. 符合 React 18 的最佳实践:使用 createRoot API 来创建和管理 React 渲染树。

  5. 更好的资源管理:提供了 dispose 方法来清理资源,有助于防止内存泄漏。

  6. 与应用的其他部分解耦:模态框的渲染和管理完全独立,不会影响主应用的渲染周期。 使用这种方法,你可以轻松地在任何 React 应用中集成这个模态框管理器,而不需要修改应用的整体结构。

这种模式也可以扩展到其他需要独立管理和渲染的 UI 元素,如全局通知、弹出提示等。