【问题标题】:How to handle window event listeners in react如何在反应中处理窗口事件侦听器
【发布时间】:2022-10-04 18:39:04
【问题描述】:

在反应中,我需要能够打开一个弹出窗口https://developer.mozilla.org/en-US/docs/Web/API/Window/open 并管理诸如“消息”https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage 和“加载”和“关闭”事件之类的事件。

但是,我添加侦听器的事件都没有触发......

import * as React from 'react';
import './style.css';
import { useState, useRef } from 'react';

export default function App() {
  const { login, error } = useOAuth();

  return (
    <div>
      <button onClick={login}>Login</button>
    </div>
  );
}

const useOAuth = () => {
  const [error, setError] = useState();
  const popupRef = useRef<Window | null | undefined>();

  const login = () => {
    popupRef.current = openPopup('https://google.com');
    popupRef.current.addEventListener('load', handlePopupLoad);
    popupRef.current.addEventListener('close', handlePopupClose);
    popupRef.current.addEventListener('message', handlePopupMessage);
  };

  const handlePopupLoad = (data) => {
    console.log('load', data);
  };

  const handlePopupClose = (data) => {
    console.log('close', data);
  };

  const handlePopupMessage = (data) => {
    console.log('message', data);
  };

  const openPopup = (url: string) => {
    const params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
        width=500,height=600,left=100,top=100`;

    return window.open(url, 'Login', params);
  };

  return {
    login,
    error,
  };
};

https://stackblitz.com/edit/react-ts-qlfw9q?file=App.tsx

在旁边:

  1. 有没有办法区分“用户”使用“红色 x”按钮关闭窗口和使用 window.close() 正确关闭窗口的时间。
  2. 如何在弹出窗口关闭后很好地清理它。

【问题讨论】:

  • 这不是反应故障。它也不适用于香草 js
  • 我知道,我也很惊讶它不起作用
  • 如果你在同一个来源,在子页面,你可以做window.parent.postMessage("some message"),在父窗口,你可以添加一个事件监听器:window.addEventListener('message', handlePopupMessage)
  • 是的。因此,我们可以将消息 eventListener 添加到我们的窗口对象,而不是我们孩子的窗口,反之亦然。

标签: javascript reactjs


【解决方案1】:

我已将 URL 更改为本地 URL(以避免任何跨域问题)。

Check out the demo(如果加载失败,请尝试刷新。Stackblitz 似乎有些问题)

在父页面中,我使用了onload(用于加载)、onunload(用于关闭)

import * as React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/auth" element={<AuthPage />} />
      </Routes>
    </BrowserRouter>
  );
}

function Home() {
  const login = () => {
    console.clear();

    const url = '/auth';
    const popup = openPopup(url);

    // When the popup loads
    popup.onload = () => {
      console.log('loaded. this was logged');
    };

    // when the popup unloads
    popup.onunload = () => {
      console.log('unloading now');
    };

    // when the popup posts a message
    popup.addEventListener('message', ({ data }) => {
      console.log('message: ', data);
    });
  };

  const openPopup = (url: string) => {
    const params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
        width=500,height=600,left=100,top=100`;

    return window.open(url, 'Login', params);
  };

  return (
    <div>
      <h1>Home</h1>
      <button onClick={login}>Login</button>
    </div>
  );
}

function AuthPage() {

  // I have added a button to trigger postMessage to parent.
  const onClick = () => {
    window.parent.postMessage('To parent');
  };

  return (
    <div>
      <h1>Auth Page</h1>
      <button onClick={onClick}>Click me</button>
    </div>
  );
}

我观察到的几件事:

  • 由于我们将消息从孩子发送到父母,我希望window.addEventListener('message') 被触发。但是,出于某种原因,popupRef.current.addEventListener('message') 被触发了。
  • popupRef.current.onunloadonload 开始之前被触发。如果我不得不猜测,这是某种清理机制。

【讨论】:

  • 刚刚有机会在反应中对此进行测试,onload 不会触发你也可以解释一下` if (popupRef.current === null) return;`
  • 消息也没有,要使消息正常工作,我必须恢复使用 window 而不是 popupRef。
  • 也许使用 next.js / stackblitz 会改变一些东西:/
  • 最后我注意到弹出窗口第一次加载时卸载触发但弹出窗口关闭时不触发
  • 是的,我在 stackblitz 中看到了它的工作原理,奇怪的是它在我的本地机器上不起作用。我想知道它是否与您在弹出窗口中加载的内容有关。我今天晚些时候再试一次然后回来
【解决方案2】:

我认为没有特定的反应问题,你需要做的就是改变你的登录功能,就像这样。

const login = () => {
    const childWindow = openPopup('https://same-origin.com');
    childWindow.addEventListener('load', handlePopupLoad);
    childWindow.addEventListener('close', handlePopupClose);
    childWindow.addEventListener('message', handlePopupMessage);
  };

【讨论】:

  • 并且不要忘记同源策略安全要求
  • 这与 OP 的 login 函数有何不同?
  • 区别在于openPopup('https://same-origin.com') 尤其是same-origin.com 我想说的是没有必要在 ref.current 中存储子窗口引用,并且由于同源安全策略而不会调用侦听器
【解决方案3】:

好吧,您可能应该将所有事件侦听器包装在 useEffect 中以运行它并在它之后进行清理,它应该看起来像这样

const popupRef = useRef<Window | null>(null)

    const handlePopupLoad = (data: any) => {
        console.log('load', data)
    }

    const handlePopupClose = (data: any) => {
        console.log('close', data)
    }

    const handlePopupMessage = (data: any) => {
        console.log('message', data)
    }

    const openPopup = (url: string) => {
        const params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
        width=500,height=600,left=100,top=100`

        return window.open(url, 'Login', params)
    }

    useEffect(() => {
        if (!popupRef.current) {
            return undefined
        }

        popupRef.current = openPopup('https://google.com')
        popupRef.current?.addEventListener('load', handlePopupLoad)
        popupRef.current?.addEventListener('close', handlePopupClose)
        popupRef.current?.addEventListener('message', handlePopupMessage)

        return () => {
            popupRef.current?.removeEventListener('load', handlePopupLoad)
            popupRef.current?.removeEventListener('close', handlePopupClose)
            popupRef.current?.removeEventListener('message', handlePopupMessage)
        }
    }, [popupRef])

【讨论】:

  • 清理事件监听器总是一个好主意,但我认为这不是 OP 问题背后的原因。
猜你喜欢
  • 1970-01-01
  • 2013-01-11
  • 2011-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多