[譯]React中的使用者認證(登入態管理)
原文地址 : kentcdodds.com/blog/authen…
本文主要展示在當下 React
應用開發中,怎麼使用 Context
和 Hooks
來管理使用者的認證(也就是登入態)。
先說結論
下面是本文最終要實現的的簡化版,方便大佬們直接看最後的效果:
import 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 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
裡渲染什麼介面,完全是開發者來決定。或許他們會渲染一些 router
,甚至會複用一些公共元件。無論具體渲染什麼,我們都不用關心使用者的登入態了,因為在渲染這兩個元件之一的時候,已經明確知道了使用者的登入狀態。
怎麼一步步實現上面的邏輯
如果你想看看最終整個APP的實現,可以到 這個github 檢視。
OK,我們接下來看看怎麼一步步來實現上面的邏輯。首先,我們看下我們應用的入口程式碼:
import 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 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
複製程式碼
OK,我們有兩個 Provider
: 一個是維護應用的認證狀態;另一個是維護當前登入使用者的資料。按照字面意思,AppProvider
負責初始化整個APP的資料(如果在localStorage
中存在使用者認證後的token
,那麼我們可以直接從token
中獲取一些使用者資料)。另一方面,UserProvider
負責將我們對使用者資料的一些修改(比如email地址、履歷等)保持和伺服器端的同步。
完整的auth-context.js 包含一些和本文主題無關的邏輯,因此下面我們來看下簡化版的 auth-context.js
:
import React from 'react'
import {FullPageSpinner} from '../components/lib'
const AuthContext = React.createContext()
function AuthProvider(props) {
// 如果我們還不確定當前使用者是否登入,比如還在請求後端介面查詢登入狀態,
// 那麼我們就渲染一個全域性的載入中,而不是載入真正的頁面元件
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
// 注意:這裡我並沒有使用 `React.useMemo` 來優化 provider 的 `value`。
// 因為這是我們應用裡最頂級的元件,很少會在這個頂級元件上觸發 重新render
return (
<AuthContext.Provider value={{data, login, logout, register}} {...props} />
)
}
const useAuth = () => React.useContext(AuthContext)
export {AuthProvider, useAuth}
// user-context.js 檔案裡的 `UserProvider` 大概長這樣:
// const UserProvider = props => (
// <UserContext.Provider value={useAuth().data.user} {...props} />
// )
// and the useUser hook is basically this:
// const useUser = () => React.useContext(UserContext)
複製程式碼
簡化我們應用裡的認證管理的關鍵點是:
負責維護使用者登入態的元件,在獲取到當前使用者的登入狀態之前,不會渲染頁面的主體內容,可以渲染一個載入中的全域性loading。只有當從伺服器端獲取到使用者的登入狀態之後,才去渲染頁面的主體:已登入就渲染登入後的元件;未登入渲染未登入的。
結論
在實際開發中,很多APP面臨的場景都是不同的。如果你採用了服務端渲染技術(SSR
),那麼你很可能不需要一個載入中的loading,因為你在服務端已經明確知道了,當前使用者是否已登入。即使在這個場景下,同樣可以將使用者的登入狀態提出到全域性的 Provider
來管理,這樣會增強我們程式碼的可維護性。
PS :
有些同學問到了相同的問題:如果我們的應用,已登入使用者和未登入使用者看到的很多介面都相同(比如twitter),而不是像gmail那樣,已登入使用者和未登入使用者看到的完全不同,我們應該怎麼來維護使用者登入狀態呢?
如果是這種情況,那麼程式碼裡很多元件,都會用到 useUser
這個hook,為了能在一個公共元件中,根據是否登入而執行不同邏輯。因為我們的公共元件可能值關心使用者是否登入,你甚至可以再封裝出一個 useIsAuthenticated
hook,返回一個 boolean
值,表示當前使用者是否已登入。多虧了 Context
和 Hooks
,要實現這樣的邏輯非常的簡單。