使用 BroadcastChannel API 出现错误

问题描述

我使用 sessionStorage 来保存身份验证信息,并且需要将此信息复制到正在打开的其他选项卡。我创建了一个 codesandbox 来重现该问题。我没有在这文章中包含代码,因为它很长。

Dashboard 链接在另一个窗口中打开时,由于弹出错误而未呈现。

编辑: 我在下面包含了所有必要的代码

App.js

export default function App() {
  return (
    <>
      <Router>
        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/login">
            <Login />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
        </Switch>
      </Router>
    </>
  );
}

Dashboard.js

export default (props) => {
  const [isLogged,setIsLogged] = useState(false);
  const [email,setEmail] = useState();
  const [token,setToken] = useState();
  const authChannel = new broadcastChannel("auth");

  useEffect(() => {
    setToken(sessionStorage.getItem("token"));
    setEmail(sessionStorage.getItem("email"));
    setIsLogged(sessionStorage.getItem("token") ? true : false);
    authChannel.postMessage({ action: "created",source: "Dashboard" });
  },[]);

  useEffect(() => {
    authChannel.onmessage = function (e) {
      if (
        e.data.action === "login" &&
        (e.data.target === "*" || e.data.target === "Dashboard")
      ) {
        setIsLogged(true);
        setEmail(e.data.data.user.email);
        setToken(e.data.data.token);
        sessionStorage.setItem("email",e.data.data.user.email);
        sessionStorage.setItem("token",e.data.data.token);
      }
    };
  },[authChannel]);

  return (
    <>
      <h1>Dashboard</h1>
      {isLogged && <h1>You are logged in</h1>}
      {!isLogged && <h1>Please log in</h1>}
    </>
  );
};

Home.js

import React,{ useState,useEffect } from "react";

export default (props) => {
  const [isLogged,setToken] = useState();
  const [user,setUser] = useState();
  const authChannel = new broadcastChannel("auth");

  useEffect(() => {
    setToken(sessionStorage.getItem("token"));
    setEmail(sessionStorage.getItem("email"));
    setIsLogged(sessionStorage.getItem("token") ? true : false);
  },[]);

  useEffect(() => {
    authChannel.onmessage = function (e) {
      if (
        e.data.action === "login" &&
        (e.data.target === "*" || e.data.target === "Home")
      ) {
        setIsLogged(true);
        setEmail(e.data.data.user.email);
        setToken(e.data.data.token);
        setUser(e.data.data.user);
        sessionStorage.setItem("email",e.data.data.token);
      } else if (e.data.action === "created") {
        authChannel.postMessage({
          action: "login",target: e.data.source,data: { user: user,token: token }
        });
      }
    };
  },[authChannel]);

  const logout = () => {
    setIsLogged(false);
    setToken(null);
    setUser({});
    setEmail();
    sessionStorage.removeItem("email");
    sessionStorage.removeItem("token");
  };

  return (
    <p>
      {isLogged && (
        <>
          <p>
            <a href="/dashboard">Dashboard</a>
          </p>
          <button onClick={() => logout()}>logout</button>
        </>
      )}
      {!isLogged && (
        <>
          <p>
            Please <a href="/login">login</a>
          </p>
        </>
      )}
    </p>
  );
};

Login.js

import React,setIsLogged] = useState(false);
  const [token,setToken] = useState();
  const [email,setEmail] = useState();
  const [user,setUser] = useState({});
  const authChannel = new broadcastChannel("auth");

  const [input,setInput] = useState({
    email: "superadmin@a.com",password: "quantum"
  });

  useEffect(() => {
    authChannel.onmessage = function (e) {
      if (e.data.action === "logout") {
        setIsLogged(false);
        setToken();
        setUser({});
      }
    };
  },[authChannel]);

  useEffect(() => {
    setToken(sessionStorage.getItem("token"));
    setEmail(sessionStorage.getItem("email"));
    setIsLogged(sessionStorage.getItem("token") ? true : false);
  },[]);

  const handleLogin = () => {
    // handle login here...
    authChannel.postMessage({
      action: "login",target: "*",data: {
        user: { email: "a@a.com" },token: "kjdhlfjkgdjgdlfghjldhjgh76lgj"
      }
    });
    setIsLogged(true);
  };

  const handleInputChange = (e,source) => {
    setInput({ ...input,[source]: e.target.value });
  };

  return (
    <div>
      {!isLogged && (
        <>
          <div>
            <label>Email</label>
            <input
              type="text"
              value={input.email || ""}
              onChange={(e) => handleInputChange(e,"email")}
            />
          </div>
          <div className="flex">
            <label>Password</label>
            <input
              type="password"
              value={input.password || ""}
              onChange={(e) => handleInputChange(e,"password")}
            />
          </div>
          <button onClick={() => handleLogin()}>Login</button>
        </>
      )}
      {isLogged && <h1>You are logged Now</h1>}
    </div>
  );
};

请告诉我这可能是什么原因。

解决方法

您收到一个错误,因为您在这里发送未定义的数据:

Home.js

authChannel.postMessage({
  action: "login",target: e.data.source,data: { user: user,token: token } //--> user and token are undefined
});

另外我建议你改变这一行:

 const authChannel = new BroadcastChannel("auth");

因为每次渲染组件时,都会创建一个新的 BroadcastChannel 实例。为避免这种情况,您需要将 BroadcastChannel 实例存储在 useRef 中:

  const authChannelRef = useRef(new BroadcastChannel("auth"));
  const authChannel = authChannelRef.current;

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...