You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何以TypeScript友好的方式编写React Router的PrivateRoute组件?

TypeScript-Friendly PrivateRoute for React Router

Great question—converting that vanilla JS PrivateRoute to TypeScript is all about adding proper type annotations to ensure type safety for your props, component, and route parameters. Let's break this down for both React Router v5 (matching your original code) and v6 (the latest widely adopted version).

React Router v5 Implementation

First, let's fix the type gaps in your original component: no type checking for the component prop, unchecked isAuthenticated state, and missing types for route props. Here's the typed version:

import React, { ComponentType } from 'react';
import { Route, RouteComponentProps, Redirect } from 'react-router-dom';

// Define props for PrivateRoute: exclude built-in 'component' from RouteProps, add our typed version
interface PrivateRouteProps<T extends object> extends Omit<RouteProps, 'component'> {
  component: ComponentType<T & RouteComponentProps>;
  isAuthenticated: boolean;
}

// Generic component to ensure the passed component receives correct route props
function PrivateRoute<T extends object>({
  component: Component,
  isAuthenticated,
  ...rest
}: PrivateRouteProps<T>) {
  return (
    <Route
      {...rest}
      render={(props) =>
        isAuthenticated ? (
          // Pass route props to the component with proper type alignment
          <Component {...props as T & RouteComponentProps} />
        ) : (
          <Redirect to="/login" />
        )
      }
    />
  );
}

// Usage example
const Dashboard = ({ match }: RouteComponentProps) => {
  return <div>Welcome to your dashboard! Current path: {match.path}</div>;
};

// In your router setup:
<PrivateRoute
  path="/dashboard"
  component={Dashboard}
  isAuthenticated={isLoggedIn.isAuthenticated}
/>

Key Type Improvements:

  • Generic Component: The <T extends object> lets TypeScript infer your component's props and ensure compatibility with route props like match, location, and history.
  • Omit<RouteProps, 'component'>: We exclude the untyped built-in component prop to define our own type-safe version.
  • ComponentType<T & RouteComponentProps>: Ensures the passed component accepts both its own props and standard React Router route props.

React Router v6 Implementation

React Router v6 replaced render/component props with element—here's the TypeScript-friendly approach for the latest version:

import React from 'react';
import { Route, Navigate, RouteProps, Outlet } from 'react-router-dom';

// Props for PrivateRoute (supports direct elements and nested routes)
interface PrivateRouteProps extends RouteProps {
  isAuthenticated: boolean;
}

// For routes with a direct element
const PrivateRoute: React.FC<PrivateRouteProps> = ({ isAuthenticated, children, ...rest }) => {
  return (
    <Route
      {...rest}
      element={isAuthenticated ? children : <Navigate to="/login" replace />}
    />
  );
};

// For nested routes (use Outlet to render child routes)
const PrivateRouteWithOutlet: React.FC<PrivateRouteProps> = ({ isAuthenticated, ...rest }) => {
  return (
    <Route
      {...rest}
      element={isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />}
    />
  );
};

// Usage examples
// Direct element:
<PrivateRoute path="/dashboard" isAuthenticated={isLoggedIn.isAuthenticated}>
  <Dashboard />
</PrivateRoute>

// Nested routes:
<PrivateRouteWithOutlet path="/app" isAuthenticated={isLoggedIn.isAuthenticated}>
  <Route index element={<Dashboard />} />
  <Route path="settings" element={<Settings />} />
</PrivateRouteWithOutlet>

Bonus: Typed Auth Context

If your isAuthenticated state comes from a React context (a common pattern), here's how to type it properly to avoid undefined issues:

import React, { createContext, useContext, ReactNode } from 'react';

// Define the shape of your auth context
interface AuthContextType {
  isAuthenticated: boolean;
  login: (credentials: { email: string; password: string }) => Promise<void>;
  logout: () => void;
}

// Create context with a default value of undefined
const AuthContext = createContext<AuthContextType | undefined>(undefined);

// Provider component to wrap your app
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = React.useState(false);

  const login = async (credentials: { email: string; password: string }) => {
    // Handle login logic, then setIsAuthenticated(true)
  };

  const logout = () => {
    setIsAuthenticated(false);
  };

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

// Custom hook to access auth context (throws error if used outside provider)
export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

// Use useAuth in PrivateRoute:
const PrivateRoute: React.FC<PrivateRouteProps> = ({ children, ...rest }) => {
  const { isAuthenticated } = useAuth();
  return (
    <Route
      {...rest}
      element={isAuthenticated ? children : <Navigate to="/login" replace />}
    />
  );
};

This ensures you never deal with undefined auth state in your components, and TypeScript enforces the shape of your auth context throughout your app.

内容的提问来源于stack exchange,提问作者Knows Not Much

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:29:25