原文地址 : kentcdodds.com/blog/authen…javascript
本文主要展現在當下 React
應用開發中,怎麼使用 Context
和 Hooks
來管理用戶的認證(也就是登陸態)。java
下面是本文最終要實現的的簡化版,方便大佬們直接看最後的效果:react
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 複製代碼
嗯,最終的代碼大概就長這樣。大多數 須要進行用戶認證管理的應用,均可以使用相似上面的邏輯來管理用戶登陸狀態。當用戶訪問咱們應用中的某個須要登陸後才能訪問的頁面時,咱們能夠將用戶重定向到登錄頁,大多數狀況下也是這樣作的,除此以外,咱們還能夠不進行跳轉,直接在該頁面上展現未登陸用戶看到的界面。爲了提升用戶體驗,咱們也能夠這樣:git
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 複製代碼
親,代碼 懶加載 就實現了:未登陸用戶訪問咱們頁面,只會加載渲染未登陸界面的代碼;已登陸用戶訪問同一個頁面,一樣只會加載渲染已登陸界面的代碼。github
具體在 AuthenticatedApp
和 UnauthenticatedApp
裏渲染什麼界面,徹底是開發者來決定。或許他們會渲染一些 router
,甚至會複用一些公共組件。不管具體渲染什麼,咱們都不用關心用戶的登陸態了,由於在渲染這兩個組件之一的時候,已經明確知道了用戶的登陸狀態。後端
若是你想看看最終整個APP的實現,能夠到 這個github 查看。服務器
OK,咱們接下來看看怎麼一步步來實現上面的邏輯。首先,咱們看下咱們應用的入口代碼:app
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
組件的代碼:dom
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地址、履歷等)保持和服務器端的同步。ide
完整的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
,要實現這樣的邏輯很是的簡單。