跳到最后
这就是这篇博文的秘密,只有一个简短的代码例子:
import * as React from 'react'
import {useUser} from './context/auth'
import AuthenticatedApp from './authenticated-app'
import UnauthenticatedApp from './unauthenticated-app'
function App() {
const user = useUser()
return user ? <AuthenticatedApp /> : <UnauthenticatedApp />
}
export App
大多数需要任何形式的认证的应用程序都可以通过这个小技巧而得到极大的简化。当用户碰巧进入一个他们不应该进入的页面时,与其试图做一些花哨的事情来重定向,不如完全不渲染这些东西。当你这样做的时候,事情变得更酷了。
import * as React from 'react'
import {useUser} from './context/auth'
const AuthenticatedApp = React.lazy(() => import('./authenticated-app'))
const UnauthenticatedApp = React.lazy(() => import('./unauthenticated-app'))
function App() {
const user = useUser()
return user ? <AuthenticatedApp /> : <UnauthenticatedApp />
}
export App
甜蜜的是,现在你甚至懒得加载代码,直到它被需要。因此,对未认证的用户来说,登录屏幕显示得更快,对认证的用户来说,应用程序加载得更快。
<AuthenticatedApp /> 和<UnauthenticatedApp /> 做什么,完全取决于你。也许他们渲染了独特的路由器。也许他们甚至使用一些相同的组件。但无论他们做什么,你都不必费心去想用户是否登录,因为你使应用程序的一边或另一边在没有用户的情况下简直无法渲染。
我们是如何做到这一点的呢?
如果你想看看这一切是如何完成的,那么你可以查看我为构建ReactJS应用程序研讨会制作的书架 repo。
好吧,那么你要怎么做才能达到这一点呢?让我们先看一下我们实际渲染应用程序的地方:
import * as React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
import AppProviders from './context'
ReactDOM.render(
<AppProviders>
<App />
</AppProviders>,
document.getElementById('root'),
)
这里是<AppProviders /> 组件:
import * as React from 'react'
import {AuthProvider} from './auth-context'
import {UserProvider} from './user-context'
function AppProviders({children}) {
return (
<AuthProvider>
<UserProvider>{children}</UserProvider>
</AuthProvider>
)
}
export default AppProviders
好的,很好,所以我们有一个应用程序的认证提供者和一个用户的数据提供者。因此,推测<AuthProvider /> 将负责引导应用程序的数据(如果用户的认证令牌已经在localStorage中,那么我们可以简单地使用该令牌检索用户的数据)。然后,<UserProvider /> 将负责在内存和服务器上保持用户数据的更新,因为我们对用户的数据(如他们的电子邮件地址/生物等)进行了修改。
auth-context.js 文件中的一些内容超出了这篇博文的范围/特定领域,所以我只展示它的一个精简/修改版本。
import * as React from 'react'
import {FullPageSpinner} from '~/components/lib'
const AuthContext = React.createContext()
function AuthProvider(props) {
// code for pre-loading the user's information if we have their token in
// localStorage goes here
// 🚨 this is the important bit.
// Normally your provider components render the context provider with a value.
// But we post-pone rendering any of the children until after we've determined
// whether or not we have a user token and if we do, then we render a spinner
// while we go retrieve that user's information.
if (weAreStillWaitingToGetTheUserData) {
return <FullPageSpinner />
}
const login = () => {} // make a login request
const register = () => {} // register the user
const logout = () => {} // clear the token in localStorage and the user data
// note, I'm not bothering to optimize this `value` with React.useMemo here
// because this is the top-most component rendered in our app and it will very
// rarely re-render/cause a performance problem.
return (
<AuthContext.Provider value={{data, login, logout, register}} {...props} />
)
}
const useAuth = () => React.useContext(AuthContext)
export {AuthProvider, useAuth}
// the UserProvider in user-context.js is basically:
// const UserProvider = props => (
// <UserContext.Provider value={useAuth().data.user} {...props} />
// )
// and the useUser hook is basically this:
// const useUser = () => React.useContext(UserContext)
大大简化你的应用程序中的认证的关键想法是这样的。
拥有用户数据的组件会阻止应用程序的其他部分被渲染,直到用户数据被检索出来或者确定没有登录的用户。
它通过简单地返回一个旋转器而不是渲染应用程序的其他部分来做到这一点。这不是在渲染一个路由器或任何东西,真的。只是一个旋转器,直到我们知道我们是否有一个用户令牌并试图获得该用户的信息。一旦完成,我们就可以继续渲染应用程序的其他部分。
总结
许多应用程序是不同的。如果你在做服务器端的渲染,那么你可能不需要一个旋转器,在你开始渲染的时候,你就已经有了用户的信息。即使在这种情况下,在你的应用程序的树上取一个更高的分支,也会大大简化你的应用程序的维护。