본문 바로가기

리액트

useContext 를 사용해서 로그인 상태를 전역적으로 관리해봅시다.

반응형

들어가는 말

저는 보통 useContext 를 사용하지 않고, 해당 훅을 내부적으로 사용하고 있는 전역 상태 라이브러리를 사용해서 쓰고 있었습니다. 과거에 다크모드 테마를 전역적으로 관리하기 위한 목적으로  useContext 를 사용한 후에는 굳이 쓸 필요성을 느끼지 못해서 눈 여겨보지는 않았죠. 

 

오늘은 이렇게 서먹해져버린 이 친구에 대해서 다시 상기해보면서 유저의 로그인 상태를 관리하는 간단한 예제를 바탕으로 다시 친해지는 시간을 가져볼까 합니다.

 

useContext 에 대해 깊이 있고, 명확한 정보는 리액트 공식 문서(https://ko.react.dev/learn/passing-data-deeply-with-context) 를 참고하면 됩니다. 해당 포스트는 간략하게 알아보는 것이 목적이므로 나름의 생각과 이유를 기반으로 글을 정리해보겠습니다.

 

useContext 가 무엇이고, 왜 사용할까요?

사실 useContext 가 필요로 하는 몇 가지 이유를 정리하면 그 자체가 해당 훅의 개념이라 할 수 있으므로 이에 대해 총 3가지 이유를 정리해보았습니다. 사용되는 예제가 로그인 상태이므로 이를 기반으로 각 이유를 언급하겠습니다.

 

전역 상태 관리 

로그인 상태는 앱 전반에 걸쳐 필요합니다. 사용자가 로그인했는지 여부에 따라 다른 화면을 보여주거나 특정 기능에 접근할 수 있도록 제어해야 합니다. useContext를 사용하면 전역에서 이 상태를 쉽게 공유할 수 있습니다.

 

Prop Drilling 방지

useContext를 사용하면 중간 컴포넌트를 거치지 않고 로그인 상태를 직접적으로 필요로 하는 컴포넌트에 전달할 수 있습니다. 이는 코드의 복잡성을 줄여주고 유지보수를 쉽게 합니다.

 

아래는 리액트 공식문서에서 프롭 드릴링의 예시를 보여주고 있습니다. 

 

 

참고로, 루트 컴포넌트에서 시작된 상태가 리프 컴포넌트 까지 전달이 되고, 해당 상태를 루트 컴포넌트에서 반영하기 위해서 또 다시 상태를 끌어올려야 하는 문제가 발생하는데, 이를 Prop Driling 이라고 합니다. 이게 문제가 되는 이유는 루트 컴포넌트와 리프 컴포넌트 사이에 무수히 많은 prop 전달을 위한 중간 컴포넌트가 존재하고, 해당 prop 을 사용하지 않음에도 불구하고, 모든 중간 컴포넌트가 상태의 변경에 의해 리렌더링되는 문제를 경험할 수 있습니다.

 

이는 결국 애플리케이션의 성능 저하를 일으키므로 프롭 드릴링 문제가 심각한 경우에는 화면이 버벅이는 문제를 경험할수도 있습니다.

 

즉, useContext 는 이러한 중간 계층을 거치지 않고 즉시 상태를 사용하는 컴포넌트로 배달하는 역할을 하기 때문에, 성능 저하를 방지할 수 있습니다.

 

재사용성

useContext를 사용한 로그인 관리 코드는 재사용성이 높습니다. 어디에서든 useContext를 호출하여 로그인 상태에 접근할 수 있습니다. 다시말해,  로그인 유무에 따른 로직의 분기처리가 필요한 곳에서 상태를 재사용할 수 있으니 재사용성이 높아지는 것은 당연한 말입니다.

 

참고로 예시는 리액트 네이티브 코드로 작성되어 있습니다. 최근에 공부 중이라 적용시켜 보았습니다.

사용 예시, 로그인 상태를 관리해보자

들어가기 전, 사용되는 예시는 아주 단순한 예시이므로 실제 코드 사용에서 적용하는 것은 부수적인 처리가 필요할 수 있습니다.

 

AuthContext 생성하기

우선, useContext 를 사용하기 위해서는 Context 를 만들어주어야 합니다. 또한, 이를 전역적으로 사용하기 위한 전역적인 Provider 컴포넌트를 생성하고, children 객체를 해당 프로바이더에 바인딩 함으로써 모든 자식 컴포넌트가 프로바이더의 영향권에 들어오도록 설정해줍니다.

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

// Context 생성
const AuthContext = createContext();

// Provider 컴포넌트
export const AuthProvider = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

// 로그인 요청 함수
  const login = () => {
    setIsAuthenticated(true);
  };

// 로그아웃 요청 함수
  const logout = () => {
    setIsAuthenticated(false);
  };

  // 여기서 프로바이더의 value 으로 전달된 상태는 children의 자리에 남겨지는 모든 컴포넌트가 사용할 수 있습니다.
  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

// useContext 로 반환받은 값을 쉽게 읽어와 사용할 수 있도록 커스텀 훅을 만들어주었습니다.
export const useAuth = () => {
  return useContext(AuthContext); // useContext 의 자리에는 {isAuth..., login, logout} 이 남습니다.
};
- AuthContext: 로그인 상태를 관리하기 위한 Context입니다.
- AuthProvider: 로그인 상태와 로그인/로그아웃 기능을 자식 컴포넌트에 제공할 수 있습니다.
- useAuth: useContext를 사용하여 로그인 상태와 관련된 함수들을 간편하게 사용할 수 있도록 하는 커스텀 훅입니다.

 

로그인 상태 사용하기

앞서 useAuth 으로 useContext 를 사용하기 쉽게 만든 커스텀 훅을 상태관리의 타겟이 되는 컴포넌트에 가져와서 사용할 수 있습니다. useContext 는 인자로 전달 받은 AuthContext 로 부터 provider 의 value  에 할당했던 값을 반환하며, 여기서는 그 값이 {isAuthenticated, login, logout] 이 됩니다.

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useAuth } from './AuthContext'; // 앞에서 만든 AuthContext

const HomeScreen = () => {
  const { isAuthenticated, login, logout } = useAuth();

  return (
    <View>
      {isAuthenticated ? (
        <>
          <Text>Welcome, you are logged in!</Text>
          <Button title="Logout" onPress={logout} />
        </>
      ) : (
        <>
          <Text>You are not logged in.</Text>
          <Button title="Login" onPress={login} />
        </>
      )}
    </View>
  );
};

export default HomeScreen;
- isAuthenticated: 사용자가 로그인했는지 여부를 나타냅니다.
- login/logout: 사용자가 로그인하거나 로그아웃할 때 호출되는 함수입니다.
- HomeScreen: 로그인 상태에 따라 다른 UI를 렌더링하는 컴포넌트입니다.

 

프로바이더 감싸기

마지막으로 AuthContext 를 실제 적용할 컴포넌트를 AuthProvider 로 감싸줍니다. 이렇게 되면 앞서 설명한 방식대로 privder 의 value 프로퍼티로 전달한 값을 useContext 로 부터 반환받고, 이 값을 전역적으로 사용할 수 있게 됩니다.

import React from 'react';
import { AuthProvider } from './AuthContext';
import HomeScreen from './HomeScreen';

const App = () => {
  return (
    <AuthProvider>
      <HomeScreen />
    </AuthProvider>
  );
};

export default App;
AuthProvider: 이 컴포넌트가 앱 전체를 감싸고 있으므로, useAuth를 사용하여 로그인 상태와 관련된 데이터에 접근할 수 있습니다.

 

 

[나가는 말] useContext 사용 시 고려할 점

이번에 아주 간단한 예시를 통해 useContext 를 알아보았습니다. 활용하기에 따라서는 전역상태 라이브러리를 사용하지 않고도 충분히 해당 훅을 통해 상태관리가 가능할 것으로 보이지만, useContext 를 사용하는 모든 컴포넌트에서 값의 변동이 발생하면 모든 구독된 컴포넌트 또한 리렌더링이 발생할 수 있기 때문에, 조심할 필요가 있어 보입니다.

 

또한, 상태가 복잡할수록 상태 분리가 생각보다 어려울 수 있는 생각도 듭니다. 즉, 여러 상태를 관리하는 context 가 산재하는 경우에는 각 상태를 모듈화하여 사용하기에 사실상 관리가 복잡해질 수 있다고 봅니다.

 

특히, 특정한 상태에 대해서 식별하고 이를 구독하는 방식으로 업데이트, 조회되는 방식이 아니기 때문에 특정 상태를 변경하는 것임에도 불구하고, 관련없는 다른 상태 또한 같이 업데이트될 수 있다는 제한점은 복잡한 상태관리를 요구하는 경우에는 사용성이 제한될 수 있다는 생각도 듭니다.

 

그럼에도 별도 라이브러리 설치없이 내장되어 있는 훅이므로 그에 따른 유지보수와 활용성은 무궁무진하다고 생각되므로 조금씩 이를 활용할 수 있는 방법들을 찾아서 적용해보면 좋을 것 같습니다.

 

 

반응형