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,并在实例化 store
(preloadedState
参数)对象时从 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 - 即使刷新了如何保持在同一页面上?的主要内容,如果未能解决你的问题,请参考以下文章