Въведение

В този урок ще научим как да управляваме глобалното състояние с помощта на контекстния API на React и куката useReducer. Ще изградим система за удостоверяване, която съхранява потребителски данни и статус на удостоверяване, което ги прави достъпни в цялото приложение.

Предпоставки

Преди да започнем, уверете се, че сте инсталирали следното:

  • Node.js и npm
  • Редактор на код (Код на Visual Studio, Sublime Text и др.)
  • Основни познания за React и функционални компоненти

Настройвам

Създайте нов React проект, като използвате Create React App или предпочитания от вас метод:

npx create-react-app react-auth-context-demo --template typescript
cd react-auth-context-demo
npm start

Част 1: Внедряване на интерфейси за контекст на удостоверяване

Нека започнем със създаването на необходимите интерфейси за нашия Auth Context.

// Import necessary dependencies
import { createContext, useEffect, useReducer } from 'react';
import type { FC, ReactNode } from 'react';
import PropTypes from 'prop-types';
import type { User } from '../types/user';
import { authApi } from '../__fakeApi__/authApi';

// Define the structure of the state object
interface State {
  isInitialized: boolean;
  isAuthenticated: boolean;
  user: User | null;
}

// Define the AuthContextValue interface extending the State interface with additional methods
interface AuthContextValue extends State {
  platform: 'JWT';
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  register: (email: string, name: string, password: string) => Promise<void>;
}

// Define the AuthProviderProps interface for specifying props of AuthProvider
interface AuthProviderProps {
  children: ReactNode;
}

// Rest of the code will be added in subsequent parts.

Част 2: Дефиниране на типове действия и начално състояние

ще дефинираме типовете действия и първоначалното състояние за нашия контекст на удостоверяване.

// ... (Previous code)

// Define action types to be used by the reducer function
type InitializeAction = {
  type: 'INITIALIZE';
  payload: {
    isAuthenticated: boolean;
    user: User | null;
  };
};

type LoginAction = {
  type: 'LOGIN';
  payload: {
    user: User;
  };
};

type LogoutAction = {
  type: 'LOGOUT';
};

type RegisterAction = {
  type: 'REGISTER';
  payload: {
    user: User;
  };
};

type Action =
  | InitializeAction
  | LoginAction
  | LogoutAction
  | RegisterAction;

// Define the initial state of the authentication context
const initialState: State = {
  isAuthenticated: false,
  isInitialized: false,
  user: null
};

// ... (Rest of the code will be added in the next part.)

Част 3: Създаване на редуктор и манипулатори

В тази част ще създадем редуцираща функция и манипулатори за актуализиране на състоянието въз основа на различни типове действия.

// ... (Previous code)

// Define handlers for each action type to update the state
const handlers: Record<string, (state: State, action: Action) => State> = {
  INITIALIZE: (state: State, action: InitializeAction): State => {
    const { isAuthenticated, user } = action.payload;

    return {
      ...state,
      isAuthenticated,
      isInitialized: true,
      user
    };
  },
  LOGIN: (state: State, action: LoginAction): State => {
    const { user } = action.payload;

    return {
      ...state,
      isAuthenticated: true,
      user
    };
  },
  LOGOUT: (state: State): State => ({
    ...state,
    isAuthenticated: false,
    user: null
  }),
  REGISTER: (state: State, action: RegisterAction): State => {
    const { user } = action.payload;

    return {
      ...state,
      isAuthenticated: true,
      user
    };
  }
};

// Reducer function to manage state updates based on actions
const reducer = (state: State, action: Action): State => (
  handlers[action.type] ? handlers[action.type](state, action) : state
);

// ... (Rest of the code will be added in the next part.)

Част 4: Създаване на AuthContext и AuthProvider

ще създадем AuthContext с помощта на createContext и компонента AuthProvider с помощта на useReducer и useEffect.

// ... (Previous code)

// Create the AuthContext using createContext
const AuthContext = createContext<AuthContextValue>({
  ...initialState,
  platform: 'JWT',
  login: () => Promise.resolve(),
  logout: () => Promise.resolve(),
  register: () => Promise.resolve()
});

// Create the AuthProvider component using useReducer and useEffect
export const AuthProvider: FC<AuthProviderProps> = (props) => {
  const { children } = props;
  const [state, dispatch] = useReducer(reducer, initialState);

  // useEffect to initialize the authentication state
  useEffect(() => {
    const initialize = async (): Promise<void> => {
      try {
        const accessToken = window.localStorage.getItem('accessToken');

        if (accessToken) {
          const user = await authApi.me(accessToken);

          // Dispatch INITIALIZE action with user data if accessToken is present
          dispatch({
            type: 'INITIALIZE',
            payload: {
              isAuthenticated: true,
              user
            }
          });
        } else {
          // Dispatch INITIALIZE action with default data if no accessToken is present
          dispatch({
            type: 'INITIALIZE',
            payload: {
              isAuthenticated: false,
              user: null
            }
          });
        }
      } catch (err) {
        console.error(err);
        // Dispatch INITIALIZE action with default data in case of any error
        dispatch({
          type: 'INITIALIZE',
          payload: {
            isAuthenticated: false,
            user: null
          }
        });
      }
    };

    // Call the initialize function on component mount
    initialize();
  }, []);

  // Define login, logout, and register functions to handle authentication actions
  const login = async (email: string, password: string): Promise<void> => {
    // ... (Rest of the code will be added in the next part.)
  };

  // ... (Rest of the code will be added in the next part.)
};

// ... (Rest of the code will be added in the next part.)

Част 5: Завършване на AuthProvider и PropTypes

ще завършим компонента AuthProvider и ще добавим проверка на PropTypes.

// ... (Previous code)

export const AuthProvider: FC<AuthProviderProps> = (props) => {
  const { children } = props;
  const [state, dispatch] = useReducer(reducer, initialState);

  // ... (Previous code)

  // Define login, logout, and register functions to handle authentication actions
  const login = async (email: string, password: string): Promise<void> => {
    const accessToken = await authApi.login({ email, password });
    const user = await authApi.me(accessToken);

    localStorage.setItem('accessToken', accessToken);

    dispatch({
      type: 'LOGIN',
      payload: {
        user
      }
    });
  };

  const logout = async (): Promise<void> => {
    localStorage.removeItem('accessToken');
    dispatch({ type: 'LOGOUT' });
  };

  const register = async (
    email: string,
    name: string,
    password: string
  ): Promise<void> => {
    const accessToken = await authApi.register({ email, name, password });
    const user = await authApi.me(accessToken);

    localStorage.setItem('accessToken', accessToken);

    dispatch({
      type: 'REGISTER',
      payload: {
        user
      }
    });
  };

  // Render the AuthContext.Provider with the state and methods provided
  return (
    <AuthContext.Provider
      value={{
        ...state,
        platform: 'JWT',
        login,
        logout,
        register
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

// Validate the props for AuthProvider
AuthProvider.propTypes = {
  children: PropTypes.node.isRequired
};

// Export the AuthContext as default
export default AuthContext;

Част 6: Създаване на useAuth Hook за използване на AuthContext

import { useContext } from 'react';
import AuthContext from '../contexts/AuthContext';

const useAuth = () => useContext(AuthContext);

export default useAuth;

Част 7: Обвиване в index.js

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { AuthProvider } from './context/AuthContext';

ReactDOM.render(
  <React.StrictMode>
    {/* Wrap your App component with the AuthProvider */}
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

Използване на useAuth Hook в компонент

import React from 'react';
import useAuth from '../hooks/useAuth';

const SomeComponent: React.FC = () => {
  // Use the custom hook to get access to the AuthContext
  const { isAuthenticated, user, login, logout, register } = useAuth();
  // Now, you can use the state and methods wherever needed
  return (
    <div>
      {isAuthenticated ? (
        <p>Hello, {user?.name}!</p>
      ) : (
        <p>Please log in or register.</p>
      )}

      {isAuthenticated ? (
        <button onClick={logout}>Logout</button>
      ) : (
        <button onClick={() => login('[email protected]', 'password')}>
          Login
        </button>
      )}
    </div>
  );
};

export default SomeComponent;

Заключение

В тази последна част създадохме персонализирана кука, наречена useAuth, за да използва AuthContext. Използвайки тази кука, можете лесно да получите достъп до глобалното състояние и методите за удостоверяване във всеки компонент във вашето приложение.

Чрез комбиниране на AuthContext, AuthProvider и useAuth hook, вие вече успешно внедрихте глобално управление на състоянието за удостоверяване на потребителя във вашето приложение React. Вече можете да създавате сигурни и богати на функции приложения, които разчитат на това състояние на глобално удостоверяване.

Чувствайте се свободни да персонализирате и разширите тази реализация, за да отговаря на специфичните изисквания на вашия проект. Приятно кодиране!

Полезни ресурси: