记:利用React.createPortal开发项目Modal组件

4月底刚刚入职新公司,在经过一周的熟悉后,开始参与了一个项目的需求实现。(写bug才是最快融入团队的方式)

需求:

给xx页面(h5)用户输入信息后新增一个二次确认弹窗。

接到的第一个需求是在某个页面信息输入后展示一个二次确认的弹窗,按照习惯,我随手就写了以下代码:

    const Modal = (isOk) => {
        return (
            { isOk 
                ? <div>...<button>确认</button><bottun>取消</button></div>null
            }
        )
    }
    
    ......
    
    return (
        ...
        <Modal isOk={isOk}/>
        ...
    )
复制代码

完成

测试:

写完代码,进行测试,页面竖屏的时候,没什么问题,但是当进行横屏调试时,问题就来了,本身我写的这个组件的父组件就已经是个弹窗了,当在横屏时,父组件的form表单内容过多,所以需要上下滚动,并且这个父组件的弹窗因为有动画效果,所以已经设置了transform属性(设置了transform属性的父元素会使其子元素fixed定位相对它而不是页面),导致了当弹出二次确认的弹窗,二次确认的弹窗遮罩层会随着滚动条滚动,下方会出现明显的背景色差。那么应该如何解决这个问题呢,

    1. 干掉父组件的transfrom属性
    1. 将二次确认弹窗移到当前弹窗之外

显然,动画是不能去掉的,所以我们就只能将二次确认的弹窗移除父组件,开干

修改代码:

经过思索,我修改了上面的代码,将modal组件动态插入到了外层的div里,代码如下:

    const modal = (isOk) => {
        if (!isOk) {
            return;
        }
        ref.current = document.createElement('div');
        const content = (<div>...<button>确认</button><bottun>取消</button></div>)
        document.body.appendChild(ref.current);
        ReactDOM.render(content, ref.current);
    }
复制代码

我动态创建了一个div将其放入body内,然后在它上面渲染了弹窗,这样实现看起来没什么问题,进行自测,完美!遂提代码

code review

因为刚入职,所以是大佬重点关注对象(提交代码必看),大佬在看完我的提交之后,给我的反馈是,这个地方不需要这么写,实现的不行,不够优雅,可以使用react的传送门来写。此时,标题出现了,此前写项目,像弹窗这样的组件,vue封装的比较多,但是react基本没有(antd一把梭),于是马上开始查资料,看了看传送门相关的介绍

createPortal 传送门

createPortal是react提供的,将子节点渲染到DOM节点中的方式,该节点存在于DOM组件的层级之外,和ReactDOM.render比较:

    1. 它不会直接渲染dom,而是将react节点绑定在当前节点上
    1. ReactDOM.render这种形式的modal,每次的展示与隐藏都会创建和销毁节点,而createPortal在当前页面下只会创建一次

createPortal实现Modal

将Modal组件化,而不是放在单个页面内,内部的子元素可以根据页面的不同需求进行自定义,以下代码还可以继续扩展,动画,样式,生性周期方法等等。。

// Modal.tsx
const Dialog = ({children, isOpen}: PropsType) => {
    const ref = useRef<HTMLElement>();
  
    useEffect(() => {
      if (ref.current) {
        return;
      }
      ref.current = document.createElement('div');
      ref.current.classList.add('dialog');
      document.body.appendChild(ref.current);
      return ()=> {
        // 页面销毁时,移除外层元素
        document.body.removeChild(ref.current as Node);
      }
    }, [])
  
    return (ref.current 
        ? ReactDOM.createPortal((
            <div className={'dialog-content'} aria-hidden={isOpen}>
              {children}
            </div>
          ), ref.current) 
        : null)
  }
  
  // Page.tsx
  ...
       <Dialog isOpen={isOpen}>
        {
          <div>
            createPortal is good
          </div> 
        }
      </Dialog>
  ...
复制代码

收工

以上就是我个人的一个业务需求开发优化的过程,从完成任务 -> bug修改 -> 最佳实践,其中还学到了之前被自己忽略的知识。还要感谢大佬的review

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享