[譯]React中的使用者認證(登入態管理)

人人貸大前端技術中心發表於2019-05-28

[譯]React中的使用者認證(登入態管理)

原文地址kentcdodds.com/blog/authen…

本文主要展示在當下 React 應用開發中,怎麼使用 ContextHooks 來管理使用者的認證(也就是登入態)。

先說結論

下面是本文最終要實現的的簡化版,方便大佬們直接看最後的效果:

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
複製程式碼

親,程式碼 懶載入 就實現了:未登入使用者訪問我們頁面,只會載入渲染未登入介面的程式碼;已登入使用者訪問同一個頁面,同樣只會載入渲染已登入介面的程式碼。

具體在 AuthenticatedAppUnauthenticatedApp 裡渲染什麼介面,完全是開發者來決定。或許他們會渲染一些 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 值,表示當前使用者是否已登入。多虧了 ContextHooks,要實現這樣的邏輯非常的簡單。

相關文章