React - 即使刷新了如何保持在同一页面上?

Posted

技术标签:

【中文标题】React - 即使刷新了如何保持在同一页面上?【英文标题】:React - How to stay on the same page even if it was refreshed? 【发布时间】:2021-10-20 20:14:22 【问题描述】:

我正在使用 react-router 作为不同页面的链接。一切正常,但是,一旦我刷新页面,它会进入登录页面片刻,然后会返回主页。更糟糕的是,如果我去管理页面,刷新页面会将用户引导到登录页面,但是,用户仍然登录并且只显示登录页面。我也在使用 Firebase Firestore 和 firebase 身份验证。

app.js

const App = (props) => 
  const  setCurrentUser, currentUser  = props;
  const admin = checkUserAdmin(currentUser);
  console.log(admin);

  useEffect(() => 
    const authListener = auth.onAuthStateChanged(async (userAuth) => 
      if (userAuth) 
        const userRef = await handleUserProfile(userAuth);
        userRef.onSnapshot((snapshot) => 
          setCurrentUser(
            id: snapshot.id,
            ...snapshot.data(),
          );
        );
      

      setCurrentUser(userAuth);
    );

    return () => 
      authListener();
    ;
  , []);

  return (
    <div className="App">
      <Switch>
        <Route
          exact
          path="/"
          render=() => (
            <MainLayout>
              <Homepage />
            </MainLayout>
          )
        />
        <Route
          exact
          path="/login"
          render=() => (
            <MainLayout>
              <LoginPage />
            </MainLayout>
          )
        />
        <Route
          exact
          path="/profile"
          render=() => (
            <WithAuth>
              <MainLayout>
                <ProfilePage />
              </MainLayout>
            </WithAuth>
          )
        />
        <Route
          exact
          path="/admin"
          render=() => (
            <WithAdmin>
              <AdminHome />
            </WithAdmin>
          )
        />
      
      </Switch>
    </div>
  );
;

const mapStateToProps = ( user ) => (
  currentUser: user.currentUser,
);

const mapDispatchToProps = (dispatch) => (
  setCurrentUser: (user) => dispatch(setCurrentUser(user)),
);

export default connect(mapStateToProps, mapDispatchToProps)(App);

withAuth - 限制页面的用户。如果 currentUser 是访客用户,它会将用户定向到登录页面。

import  useAuth  from "./../custom-hooks";
import  withRouter  from "react-router-dom";

const WithAuth = (props) => useAuth(props) && props.children;

export default withRouter(WithAuth);

useAuth - 限制页面的用户。如果 currentUser 是访客用户,它会将用户定向到登录页面。

const mapState = ( user ) => (
  currentUser: user.currentUser,
);

const useAuth = (props) => 
  const  currentUser  = useSelector(mapState);

  useEffect(() => 
    if (!currentUser) 
      props.history.push("/login");
    
  , [currentUser]);

  return currentUser;
;

export default useAuth;

withAdmin - 只有管理员可以访问的页面

import  useAdmin  from "../../custom-hooks";

const WithAdmin = (props) => useAdmin(props) && props.children;

export default WithAdmin;

useAdmin - 只有管理员可以访问的页面。如果用户不是管理员,它会将用户定向到登录页面。

const mapState = ( user ) => (
  currentUser: user.currentUser,
);

const useAdmin = (props) => 
  const  currentUser  = useSelector(mapState);
  const history = useHistory();

  useEffect(() => 
    if (!checkUserAdmin(currentUser)) 
      history.push("/login");
    
  , [currentUser]);

  return currentUser;
;

export default useAdmin;

下面是我的 index.js

ReactDOM.render(
  <React.StrictMode>
    <Provider store=store>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

减速机: 用户类型:

const userTypes = 
  SET_CURRENT_USER: "SET_CURRENT_USER",
;

export default userTypes;

用户操作:

import userTypes from "./user.types";

export const setCurrentUser = (user) => (
  type: userTypes.SET_CURRENT_USER,
  payload: user,
);

userReducer:

import userTypes from "./user.types";

const INITIAL_STATE = 
  currentUser: null,
;

const userReducer = (state = INITIAL_STATE, action) => 
  switch (action.type) 
    case userTypes.SET_CURRENT_USER:
      return 
        ...state,
        currentUser: action.payload,
      ;
    default:
      return state;
  
;

export default userReducer;

rootReducer:

import  combineReducers  from "redux";

import userReducer from "./user/user.reducer";

export default combineReducers(
  user: userReducer,
);

store.js

import  createStore, applyMiddleware  from "redux";
import logger from "redux-logger";

import rootReducer from "./rootReducer";

export const middlewares = [logger];

export const store = createStore(rootReducer, applyMiddleware(...middlewares));

export default store;

checkUserAdmin.js

export const checkUserAdmin = (currentUser) => 
  if (!currentUser || !Array.isArray(currentUser.roles)) return false;
  const  roles  = currentUser;
  if (roles.includes("admin")) return true;

  return false;
;

从 App.js,我 console.log(currentUser) 这就是显示的内容:

【问题讨论】:

只是一个次要的、无关的、关于命名约定的观点......名称如WithAdmin,或任何带有with-前缀的函数,人们会期望它是一个高阶组件一个包装组件。话虽这么说,您遇到的问题是您的 redux 存储存在于内存中,因此当页面重新加载时,您的应用程序也是如此,并且可能需要一些时间来重新填充您的 user 状态切片。在这些情况下,您需要使用第三种“不确定”或未决状态来表示不是关于访问的一种状态或另一种状态。 我忘了说是的,它是一个高阶组件。那些 withAuth 和 withAdmin。 抱歉,我是在指出这些 不是 HOC,但您已将它们命名为原来的样子。 有什么办法可以解决吗? 您能否将您的user 减速器代码添加到您的问题中?正如我所说,我认为这里的解决方案是使用“待处理”状态,而不是立即决定用户是否应该访问资源或被退回路由。 【参考方案1】:

我建议将authPending 状态添加到您的userReducer,最初是true,并在firestore 逻辑处理用户更改时设置/清除。

userReducer 和操作

const userTypes = 
  SET_AUTH_PENDING: "SET_AUTH_PENDING",
  SET_CURRENT_USER: "SET_CURRENT_USER",
;

const setAuthPending = pending => (
  type: userTypes.SET_AUTH_PENDING,
  payload: pending,
);

const INITIAL_STATE = 
  authPending: true,
  currentUser: null,
;

const userReducer = (state = INITIAL_STATE, action) => 
  switch (action.type) 
    case userTypes.SET_CURRENT_USER:
      return 
        ...state,
        authPending: false
        currentUser: action.payload,
      ;

    case userTypes.SET_AUTH_PENDING:
      return 
        ...state,
        authPending: action.payload,
      ;

    default:
      return state;
  
;

app.js

const App = (props) => 
  const 
    setAuthPending, // <-- access action
    setCurrentUser,
    currentUser
   = props;

  const admin = checkUserAdmin(currentUser);
  console.log(admin);

  useEffect(() => 
    const unsubscribe = auth.onAuthStateChanged(async (userAuth) => 
      setAuthPending(true); // <-- start auth pending
      if (userAuth) 
        const userRef = await handleUserProfile(userAuth);
        userRef.onSnapshot((snapshot) => 
          setCurrentUser( // <-- will clear auth pending
            id: snapshot.id,
            ...snapshot.data(),
          );
        );
       else  
        setCurrentUser(null); // <-- clear user data and pending
      
    );

    return () => 
      unsubscribe();
    ;
  , []);

  return (
    <div className="App">
      <Switch>
        ...
      </Switch>
    </div>
  );
;

const mapStateToProps = ( user ) => (
  currentUser: user.currentUser,
);

const mapDispatchToProps = 
  setAuthPending, // <-- wrap action creator in call to dispatch
  setCurrentUser,
;

挂钩和包装

对于这些,我建议将逻辑抽象为自定义 Route 组件。

const AuthRoute = props => 
  const  authPending, currentUser  = useSelector(state => state.user);

  if (authPending) 
    return "Loading..."; // or maybe a loading spinner
  ;

  return currentUser ? (
    <Route ...props />
  ) : (
    <Redirect to="/login" />
  );
;

const AdminRoute = props => 
  const  authPending, currentUser  = useSelector(state => state.user);

  if (authPending) 
    return "Loading..."; // or maybe a loading spinner
  ;

  return checkUserAdmin(currentUser) ? (
    <Route ...props />
  ) : (
    <Redirect to="/login" />
  );
;

那么路线就变成了

<Switch>
  <Route
    exact
    path="/"
    render=() => (
      <MainLayout>
        <Homepage />
      </MainLayout>
    )
  />
  <Route
    exact
    path="/login"
    render=() => (
      <MainLayout>
        <LoginPage />
      </MainLayout>
    )
  />
  <AuthRoute
    exact
    path="/profile"
    render=() => (
      <MainLayout>
        <ProfilePage />
      </MainLayout>
    )
  />
  <AdminRoute
    exact
    path="/admin"
    component=AdminHome
  />
</Switch>

在此之后,您可能希望考虑将您的 redux 状态持久化到 localStorage,并在实例化 storepreloadedState 参数)对象时从 localStorage 重新填充您的 redux 状态。您的应用正在加载。您可以管理自己或查看redux-persist 之类的内容。

【讨论】:

我在 app.js 中遇到了 setAuthPending 错误,它显示“未处理的拒绝 (TypeError):setAuthPending 不是函数” @AranyaDream 您是否定义了setAuthPending 动作创建者mapDispatchToProps 中将其传递给connect HOC? 抱歉,setAuthPending 确实有效。对于用户来说,它已经起作用了。对于管理员,我需要设置 redux-persist?因为,它在刷新时确实有“正在加载”这个词,但它确实将用户带到了登录页面。 @AranyaDream 这可能与checkUserAdmin 正在做的事情有关。您可以将该代码添加到您的问题中吗? @AranyaDream 您已经完全验证了在获得用户后currentUser.roles 是一个数组并且其中包含“管理员”角色?你能分享你在reducer中存储的currentUser对象并在AdminRoute组件中看到吗? (console.log( currentUser ) 在正文中)?【参考方案2】:

当用户登录时,您可以在 localStorage 中存储有关用户的一些值,例如用户名或令牌或只是登录,

localStorage.setItem(IS_LOGIN, true);

之后你可以在你的userReducer中使用它,当你初始化状态时,你可以直接判断用户是否登录。

const INITIAL_STATE = 
 isLogin: localStorage.IS_LOGIN
;

现在您可以在页面加载之前确定用户是否登录。如果你想将用户推送到登录页面,你可以在 useEffect 中使用

  useEffect(() => 
    if (!isLogin) 
      props.history.push("/login");
    
  , [isLogin]);

  return isLogin;
;

当您的应用首次加载时,userReducer 上没有用户信息,因为当页面加载时,您将被定向到登录页面。

【讨论】:

以上是关于React - 即使刷新了如何保持在同一页面上?的主要内容,如果未能解决你的问题,请参考以下文章

Django - 如何在不刷新页面的情况下保持在同一页面上?

页面重新加载后如何保持在反应(antd)表中的同一页面

react-router 如何在页面刷新时保持位置状态?

操作后如何在表格上保持相同的分页页面

分页数据,但将组保持在同一页面上

页面刷新后 React useReducer 钩子状态丢失