【问题标题】:How to access the div element with class and attach the popup to it using react?如何使用类访问 div 元素并使用反应将弹出窗口附加到它?
【发布时间】:2020-10-27 13:12:35
【问题描述】:

我正在尝试创建一个弹出对话框,以便在用户单击按钮时显示。为此,我正在使用门户网站。

我希望它看起来像下面的图片,

所以基本上,当用户点击添加按钮时,我希望弹出对话框显示如上图所示。

在弹出组件中,我想用孩子渲染叠加层。当用户点击覆盖 div 时,弹出窗口应该关闭。

我有一些不使用 Portal 就可以工作的东西,如下所示,

下面是我没有使用 Portal 的代码,

function Parent({isDialogOpen, setDialogOpen, setSomething}: Props) {
    const [isClicked, setIsClicked] = React.useState(false);
    const handleButtonClick = () => {
        if (setIsDialogOpen) setIsDialogOpen(!isDialogOpen);
        if (setSomething) setSomething(isDialogOpen);
        setIsClicked(!isClicked);
    };
    return (
        <button onClick={handleButtonClick}>click</button>
        {isDialogOpen && isClicked &&
            <Overlay>
                <Dialog>
                    //some divs
                </Dialog>
            </Overlay>
        }
    );
}


const Overlay = styled.div`
    position: fixed;
    padding-top:60px;
    bottom: 40px;
    top: 0;
    left: 0;
    right: 0;
    backdrop-filter: blur(8px);
    z-index: 100;
    display: flex;
    justify-content: center;
    align-items: center;
`;

 const Dialog = styled.div`
     padding: 16px;
     width: 384px;
     max-height: calc(100% - 200px);
     display: flex;
     flex-direction: column;
 `;

现在我正在使用下面的门户重写上面,

function Parent({isDialogOpen, setDialogOpen, setSomething}: Props) {
    const [isClicked, setIsClicked] = React.useState(false);
    const handleButtonClick = () => {
        if (setIsDialogOpen) setIsDialogOpen(!isDialogOpen);
        if (setSomething) setSomething(isDialogOpen);
        setIsClicked(!isClicked);
    };
    return (
        <button onClick={handleButtonClick}>click</button>
        {isDialogOpen && isClicked &&
            <Popup setSomething={setSomething} setIsDialogOpen={setIsDialogOpen} setIsClicked= 
            {setIsClicked}>
                <Dialog>
                    //some divs
                </Dialog>
            </Overlay>
        }
    );
}

function Popup({setIsClicked, setSomething, setIsDialogOpen, children}: Props) {
    return ReactDom.createPortal(
        <>
            <Overlay
                onClick={() => {
                    if (setIsDialogOpen) setIsDialogOpen(false);
                    if (setSomething) setSomething(true);
                    setIsClicked(false);
                }}
            >
                {children}
            </Overlay>
        </>,
        //dont know what to pass here
    );
}

基本上如上图所示,我想用对话框渲染叠加层。

现在在弹出组件中,我想创建类名为“popup”的 div,并找到带有类导航栏的 div 元素,并将此 div 弹出窗口附加到导航栏 div

并在 reactDOM.createPortal 中使用弹出类传递这个 div 元素。

我是新手,不知道该怎么做。有人可以帮我解决这个问题吗?

谢谢。

【问题讨论】:

  • 这不是您使用门户的方式。您需要将门户安装到现有的 dom 节点中,就像使用 ReactDom.render( targetNode, componentTree ) 渲染您的应用程序一样。因此,例如,您可以在主应用程序中安装一些元素,例如&lt;div id="popup-dialog" /&gt;,因此您可以像ReactDom.createPortal( document.getElementById('popup-dialog'), &lt;MyComponent /&gt;); 那样渲染成这样,您需要考虑使用门户网站的一件事,就是您要在 dom 中渲染的元素在渲染之前必须在那里。你如何处理这取决于你的实现。
  • 好的。那么如何将我上面不使用 Portal 的代码重写为使用 Portal 的代码呢?谢谢
  • 由您决定。您是想通过另一个组件控制门户,还是只是您需要在树顶部的一个 dom 节点,您可以根据需要将弹出窗口呈现到其中?如果是后者,您可以在组件第一次渲染时使用回调来创建元素并将其附加到body,然后渲染到该元素中

标签: javascript reactjs typescript


【解决方案1】:

正如我在评论中向您提到的,react 不会为您创建 dom 节点。你必须自己做。你怎么做取决于你的需要。我能想到的最基本的例子如下:

  • 组件第一次挂载时,需要创建portal并插入document.body
  • 一旦我们确定门户存在,我们就可以使用我们的 dom ref 渲染到门户中。

function Popup({setIsClicked, setSomething, setIsDialogOpen, children}: Props) {

  const [portal,setPortal] = React.useState<HTMLDivElement|null>( (document.getElementById('my-portal') as HTMLDivElement)||null);
  const createPortalIfNotExists = React.useCallback(()=>{
    if(portal===null){
      const el = document.createElement('div');
      el.id='my-portal';
      document.body.appendChild(el);
      // switch this line for the one above if you want it to be first in tree
      // document.body.insertBefore(el, document.body.firstChild);
      setPortal(document.getElementById('my-portal') as HTMLDivElement);
    }
  },[portal]);

  createPortalIfNotExists();

  if(portal===null){
    return null;
  }

  return ReactDom.createPortal(
    <>
        <Overlay
            onClick={() => {
                if (setIsDialogOpen) setIsDialogOpen(false);
                if (setSomething) setSomething(true);
                setIsClicked(false);
            }}
        >
            {children}
        </Overlay>
    </>,
    portal
  );
}

这只是一种可能的方式。还有其他更高级的用例,您可以让组件树中的某个其他元素呈现门户。但这应该足以让您入门。另外,我没有对此进行测试,因为我在这台机器上没有 tsc/tslint 所以 YMMV

【讨论】:

  • 非常感谢它的工作原理:) ref 的设置状态也很好。
  • 还有如何在弹出组件卸载时删除这个 my-portal div?
  • @stack_overflow 我强烈建议不要这样做,但如果您确定要这样做,我可以更新答案以显示如何使用 useEffect 这样做。缺点是每次你的弹出窗口隐藏时,元素都会被销毁。
  • 你能举个例子吗?实际上,我需要在卸载时将其删除是为了确保此 div 不会干扰其他页面上的其他 div。
  • @stack_overflow 只是为了跟进,您是否知道当渲染组件卸载时门户会自动删除其内容?因为当您的弹出窗口卸载时,门户的内容将自动销毁。这是因为 react 门户仍然像正常的 react vdom 树一样运行。当组件卸载时,它的内容会从 dom 中删除,对于门户网站也是如此,非常方便。只有容器div 会保留,我不知道为什么会干扰。我将门户用于各种事情,我认为它们是最好的反应功能之一。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多