React의 상태 관리: 실제 사례

September 03, 2024에서 업데이트 16 분을 읽습니다

React의 상태 관리: 실제 사례

React는 많은 개발자가 동적 클라이언트 측 애플리케이션을 구축하기 위한 프레임워크입니다. 이러한 애플리케이션의 동적 특성은 유연성과 클라이언트 측에서 가능한 확장된 기능 목록에서 비롯됩니다. 이를 통해 개발자는 몇 초 만에 브라우저에 로드되는 완전한 애플리케이션을 구축할 수 있었습니다. 정적 웹 시대에는 가능합니다(또는 매우 번거롭습니다).

이러한 가능성의 확장과 함께 상태 관리라는 개념이 등장했습니다. 클라이언트 측 애플리케이션의 복잡성이 증가함에 따라 로컬 상태를 유지해야 할 필요성이 커지면서 올바르게 처리되지 않고 확장성을 염두에 두지 않으면 그 자체로 병목 현상이 발생하게 됩니다.

이 문제는 다양한 접근 방식을 따르고 다양한 하위 문제 세트에 초점을 맞춘 많은 프레임워크에서 해결되었습니다. 따라서 각 애플리케이션의 요구 사항을 평가하고 이에 따른 올바른 접근 방식을 사용하기 위해 선택한 프레임워크의 생태계에 대한 높은 수준의 이해를 갖는 것이 중요합니다. 측정항목. 이 기사에서는 일반적인 상태 관리 문제에 대한 간략한 개요를 제공하고 이에 대한 대응으로 다양한 접근 방식(useState, Context API)을 소개하려고 합니다. 이 기사에서는 다양한 솔루션을 제시하지만 소규모 문제에만 초점을 맞추고 향후 기사에서는 더 고급 주제를 다룰 것입니다.

인증 작업 흐름

기사 전반에 걸쳐 소개된 코드는 여기에서 확인할 수 있습니다.

실시간 미리보기 링크는 여기.에서 액세스할 수 있습니다.

React 애플리케이션에 대한 인증 프로세스를 구현하는 경우를 생각해 보세요.

User Login

위의 GIF에 표시된 것처럼 사용자가 자격 증명을 사용하여 애플리케이션에 로그인하거나 가입할 수 있도록 허용하려고 합니다. 유효한 자격 증명이 제공되면 사용자는 로그인되고 애플리케이션은 자동으로 홈 페이지로 이동하며 사용자는 계속해서 애플리케이션을 사용할 수 있습니다.

마찬가지로, 사용자가 로그아웃하면 홈 페이지 리소스는 로그인 뒤에 보호되며 로그인 페이지는 사용자가 액세스할 수 있는 유일한 페이지가 됩니다.

구현 측면에서 이 워크플로를 생각하면 App이라는 기본 구성 요소가 있을 것입니다. App 구성 요소는 사용자를 홈 또는 로그인의 두 페이지 중 하나로 리디렉션하며, 사용자의 현재 상태(로그인, 로그아웃)에 따라 어느 페이지를 지정하는지 결정합니다. 사용자가 리디렉션되는 페이지에서 사용자의 현재 상태가 변경되면(예: 로그인에서 로그아웃으로 변경) 해당 페이지로 즉시 리디렉션이 트리거되어야 합니다.

State

위 그림에서 볼 수 있듯이 App 구성 요소가 현재 상태를 고려하고 해당 현재 상태를 기반으로 두 페이지(홈 또는 로그인) 중 하나만 렌더링하기를 원합니다.

사용자가 null이면 인증된 사용자가 없다는 의미이므로 자동으로 로그인 페이지로 이동하고 조건부 렌더링을 사용하여 홈 페이지를 보호합니다. 사용자가 존재하면 그 반대를 수행합니다.

이제 무엇을 구현해야 하는지 확실히 이해했으므로 몇 가지 옵션을 살펴보겠습니다. 먼저 React 프로젝트를 설정해 보겠습니다.

프로젝트 저장소에는 프런트엔드 측면을 구현하는 데 사용할 샘플 백엔드 애플리케이션이 포함되어 있습니다(여기서 주요 초점이 아니기 때문에 다루지는 않겠지만 코드는 의도적으로 단순하게 유지되었으므로 사용하는 데 어려움을 겪지 않을 것입니다) )

다음 페이지와 구성 요소를 만드는 것부터 시작합니다.

  • 홈 페이지

  • 로그인 페이지

  • 사용자 편집 페이지

  • Navbar 구성 요소

  • UserDropdown 구성 요소

여러 페이지가 있는 React 애플리케이션에는 적절한 탐색이 필요합니다. 이를 위해서는 React-router-dom을 사용하여 전역 브라우저 라우터 컨텍스트를 생성하고 다양한 React 경로를 등록할 수 있습니다.


yarn add react-router-dom

많은 독자들이 튜토리얼을 따라가는 것을 선호한다는 것을 알고 있으므로 여기에 빠른 속도를 제공하는 시작 템플릿이 있습니다. 이 스타터 브랜치는 사전 정의된 TailwindCSS JSX 구성 요소에 DaisyUI를 사용합니다. 여기에는 이미 설정된 모든 구성 요소, 페이지 및 라우터가 포함됩니다. 이 튜토리얼을 따라 간단한 단계에 따라 전체 인증 흐름을 직접 구축하는 것을 고려하고 있다면 먼저 저장소를 포크하는 것부터 시작하세요. 저장소를 포크한 후 이를 복제하고 **start-here브랜치:**에서 시작합니다.


git clone git@github.com:<your-username>/fullstack-resourcify.git

start-here 분기를 당기면 다음과 같습니다.

  • 선호하는 코드 편집기로 프로젝트를 엽니다.

  • 디렉터리를 **frontend/**로 변경합니다.

  • 설치 종속성: 원사

  • 개발 서버 시작: Yarn dev·

미리보기는 다음과 같아야 합니다.

Changing user name

Navbar(오른쪽 상단)에 렌더링된 이름은 기본 앱 구성 요소에 정의된 상태 변수입니다. 동일한 변수가 Navbar와 홈 페이지 모두에 전달됩니다. 위에 사용된 간단한 양식은 실제로 EditPage 구성 요소의 “name” 상태 변수를 업데이트합니다.

아래 제시된 두 가지 접근 방식은 구현 세부 사항에 대해 설명합니다.

첫 번째 접근 방식: useState

사용상태()

useState는 가장 일반적으로 사용되는 React 후크 중 하나이며, 이를 사용하면 React Functional 구성 요소에서 상태를 생성하고 변경할 수 있습니다. useState 구성 요소는 매우 간단한 구현을 가지고 있으며 사용하기 쉽습니다. 새 상태를 생성하려면 상태의 초기 값으로 useState를 호출해야 하며 useState 후크는 두 개의 변수가 포함된 배열을 반환합니다. 첫 번째는 상태입니다. 상태를 참조하는 데 사용할 수 있는 변수와 상태 값을 변경하는 데 사용하는 함수인 두 번째 변수는 매우 간단합니다.

실제로 실제로 보는 것은 어떨까요? Navbar(오른쪽 상단)에 렌더링된 이름은 기본 앱 구성 요소에 정의된 상태 변수입니다. 동일한 변수가 Navbar와 홈 페이지 모두에 전달됩니다. 위에 사용된 간단한 양식은 실제로 EditPage 구성 요소의 “name” 상태 변수를 업데이트합니다. 요점은 다음과 같습니다. useState는 초기 상태를 매개변수로 받아들이고 두 값을 보유하는 두 변수, 즉 초기 상태를 포함하는 상태 변수와 동일한 상태 변수에 대한 setter 함수를 반환하는 코어 후크입니다.

이를 분석하고 처음에 어떻게 구현되었는지 살펴보겠습니다.

  1. “이름” 상태 변수 생성:

./src/App.jsx


import { useState } from "react";

function App() {
const testValue = "CLA";
 //using the useState hook to create a state variable out of an initial value passed as an argument
 const [name, setName] = useState(testValue);
 console.log(`Rendering: ${name}`);

 return (...)};

소품

Props는 React 구성 요소의 기본 구성 요소 중 하나입니다. 개념적으로 React 기능 구성 요소를 Javascript 함수로 생각하면 props는 함수 매개 변수에 지나지 않으며 props와 useState 후크를 결합하면 견고한 프레임워크를 제공할 수 있습니다. 간단한 React 애플리케이션 전체에서 상태를 관리합니다.

React props는 사용자 정의 구성 요소에 속성으로 전달됩니다. props로 전달된 속성은 다음과 유사하게 인수로 받아들일 때 props 객체에서 구조 해제될 수 있습니다.

소품 전달

<Routes>
  <Route path="/" element={<Home name={name} />} /> // Passing name as a prop to
  Home Component
  <Route
    path="/user"
    element={<EditUser name={name} setName={setName} />} // passing both name and setItem function as props to EditUser component
  />
  <Route path="/login" element={<Login />} />
</Routes>

Props는 일반 함수 인수와 유사한 기능적 구성 요소 내에서 허용되고 사용될 수 있습니다. “name”이 Home 구성 요소에 prop으로 전달되기 때문에 동일한 구성 요소에서 렌더링할 수 있습니다. 다음 예에서는 props 객체에서 name 속성을 추출하기 위해 구조 분해 구문을 사용하여 전달된 prop을 허용합니다.

소품 수락

./src/pages/Home.jsx

... ...
export default function Home({name}) { //Destructuring the name property from the props object, another approach would be: Home(props.name)
 console.log("Rendering: Home");
 return (
   <div className="flex flex-col bg-white m-auto p-auto">
     <h1 className="flex py-5 lg:px-20 md:px-10 mx-5 font-bold text-2xl text-gray-800">
       Welcome {name}
     </h1>
... ...

전문가 팁

브라우저의 콘솔을 열고 상태가 변경될 때 “name” 속성을 사용하는 모든 구성 요소가 어떻게 다시 렌더링되는지 확인하세요. 상태 변수를 조작할 때 React는 다음 상태를 저장하고 새 값으로 구성 요소를 다시 렌더링하고 UI를 업데이트합니다.

Components Re-rendering

useState 단점

소품-드릴링

소품 드릴링은 구성 요소 집합에 상위 구성 요소가 제공하는 특정 소품이 필요한 구성 요소의 계층 구조를 나타내는 용어입니다. 경험이 없는 개발자가 일반적으로 사용하는 일반적인 해결 방법은 전체 구성 요소 체인에 이러한 소품을 전달하는 것입니다. 접근 방식은 이러한 prop 중 하나라도 변경하면 전체 구성 요소 체인이 다시 렌더링되도록 트리거하여 이러한 불필요한 렌더링, 이러한 prop이 필요하지 않은 체인 중간에 있는 구성 요소로 인해 전체 응용 프로그램의 속도가 효과적으로 느려진다는 것입니다. 소품을 전달하는 매개체 역할을 합니다.

예:

  • useState() 후크를 사용하여 기본 앱 구성 요소에 상태 변수가 정의되었습니다.

  • 이름 소품이 Navbar 구성 요소에 전달되었습니다.

  • Navbar에서 동일한 prop이 허용되고 UserDropdown 구성 요소에 다시 prop으로 전달됩니다.

  • UserDropdown은 prop을 허용하는 마지막 하위 요소입니다.

Prop-drilling

./src/App.jsx

... ...
function App() {
 const [name, setName] = useState(test);
 console.log("Rendering: App");

 return (
   <BrowserRouter>
     <div className="h-screen">
       <Navbar name={name} />

       <main className="px-4">
... ...

./src/comComponents/Navbar.jsx

import React from "react";
import Logo from "../assets/cla.svg";
import { BiSearchAlt } from "react-icons/bi";
import { Link } from "react-router-dom";
import UserDropdown from "./UserDropdown";

export default function Navbar({ name }) {
  console.log("Rendering: Navbar");

  return (
    <>
      <div className="navbar bg-base-100 drop-shadow-sm">
        <div className="flex-1">
          <Link
            to="/"
            className="btn btn-ghost normal-case text-md md:text-xl px-2 gap-1"
          >
            <img src={Logo} className="h-6" alt="" />
            Resources
          </Link>
        </div>

        <UserDropdown name={name} />
      </div>
    </>
  );
}

서로 다른 구성 요소 계층이 모두 동일한 상태를 전달하고 렌더링하는 React 앱을 유지하는 것이 얼마나 어려운지 상상해 보십시오.

복잡성 및 코드 품질 증가

React 애플리케이션에서 상태 관리의 유일한 수단으로 useState와 props를 사용하면 코드베이스가 빠르게 복잡해질 수 있으며, 서로 중복될 수 있고 여러 파일에 분산되어 있을 수 있는 수십 또는 수백 개의 상태 변수를 관리해야 합니다. 구성 요소는 상당히 어려울 수 있으므로, 주어진 상태 변수를 변경하려면 이미 느린 응용 프로그램에서 잠재적인 추가 다시 렌더링을 방지하기 위해 구성 요소 간의 종속성을 신중하게 고려해야 합니다.

두 번째 접근 방식: 컨텍스트 API

Context API는 상태 관리를 위해 props와 useState만 사용하는 단점을 해결하려는 React의 시도입니다. 특히 context API는 전체 컴포넌트 트리에 props를 전달해야 하는 필요성과 관련하여 이전에 언급한 문제에 대한 답변으로 제공됩니다. 컨텍스트를 사용하면 전역으로 간주되는 데이터의 상태를 정의하고 구성 요소 트리의 어느 지점에서나 해당 상태에 액세스할 수 있습니다. 더 이상 소품을 드릴링할 필요가 없습니다.

컨텍스트 API는 처음에 글로벌 데이터 공유 문제, UI 테마와 같은 데이터, 사용 사례인 인증 정보, 언어 등), 공유해야 하는 다른 유형의 데이터에 대한 문제를 해결하기 위해 고안되었다는 점을 지적하는 것이 중요합니다. 둘 이상의 구성 요소 중에서 반드시 모든 애플리케이션에 전역적일 필요는 없으며 컨텍스트가 최선의 옵션이 아닐 수도 있습니다. 사용 사례에 따라 구성 요소 구성과 같은 다른 기술을 고려할 수도 있습니다. /composition-vs-inheritance.html) 이는 이 기사의 범위를 벗어납니다.

컨텍스트로 전환

인증 컨텍스트 만들기

createContext는 간단합니다. 새 컨텍스트 변수를 생성하고 단일 선택적 매개변수(컨텍스트 변수의 기본값)를 사용합니다.

실제로 이를 살펴보겠습니다. 먼저 “contexts”라는 새 폴더를 만들고 그 안에 “Auth.jsx”라는 새 파일을 만듭니다. 새 컨텍스트를 생성하려면 createContext() 함수를 호출하고 반환된 값을 다음에 내보낼 새 변수 Auth에 할당해야 합니다.

./src/contexts/Auth.jsx

import { createContext } from "react";

export const Auth = createContext();

인증 컨텍스트 제공

이제 이전에 생성한 “Auth” 컨텍스트 변수를 노출해야 합니다. 이를 달성하기 위해 컨텍스트 공급자를 사용합니다. 컨텍스트 공급자는 “값” prop이 있는 구성 요소입니다. 이 prop을 사용하여 값(name)을 공유할 수 있습니다. – 구성 요소 트리 전체에서 구성 요소 트리를 공급자 구성 요소로 래핑하여 해당 속성을 각 하위 구성 요소에 개별적으로 전달할 필요 없이 구성 요소 트리 내부 어디에서나 해당 값에 액세스할 수 있도록 합니다.

인증 예제에서는 사용자 객체를 보유하기 위한 변수와 해당 상태 변수를 조작하기 위한 일종의 setter가 필요합니다. 이는 useState의 완벽한 사용 사례입니다. Context를 사용할 때 제공하려는 데이터를 정의하고 해당 데이터(이 예에서는 사용자)를 내부에 중첩된 모든 구성 요소 트리에 전달하는지 확인해야 합니다. 따라서 공급자 구성 요소 내에 새 상태 변수 user를 정의하고 마지막으로 공급자가 구성 요소 트리에 노출할 값으로 배열 내부의 user와 setUser를 모두 전달합니다.

./src/contexts/Auth.jsx

import { createContext, useState } from "react";

export const Auth = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  return <Auth.Provider value={[user, setUser]}>{children}</Auth.Provider>;
};

우리가 할 수 있는 또 다른 일은 “이름” 상태 변수를 기본 앱 구성 요소에서 인증 컨텍스트로 이동하고 중첩된 구성 요소에 노출하는 것입니다.

import { createContext, useState } from "react";

export const Auth = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [name, setName] = useState("CLA");
  return <Auth.Provider value={[name, setName]}>{children}</Auth.Provider>;
};

이제 남은 것은 방금 내보낸 동일한 AuthProvider 구성 요소에 앱을 중첩하는 것입니다.

./src/main.jsx:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { AuthProvider } from "./contexts/Auth";

import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <AuthProvider>
    <App />
  </AuthProvider>
);

Auth.Provider 내부에 하위 속성을 렌더링하기 때문에 AuthProvider 구성 요소 내에 중첩된 모든 요소는 이제 Auth.Provider에 전달한 값 속성을 사용할 수 있습니다. 혼란스러워 보일 수도 있지만 일단 실험해 보면 전역 컨텍스트 상태를 제공하고 사용해 보세요. 결국 Context API를 실험해본 후에야 이해가 되었습니다.

인증 컨텍스트 사용

마지막 단계는 간단합니다. 컨텍스트 후크 “useContext”를 사용하여 “Auth”의 컨텍스트 공급자가 제공하는 값(우리의 경우 user 및 setUser가 포함된 배열)에 액세스합니다. 다음 코드에서는 다음을 수행할 수 있습니다. Navbar 내부의 인증 컨텍스트를 사용하려면 useContext를 사용하세요. 이는 Navbar가 App 구성 요소 내에 중첩되어 있고 AuthProvider가 App 구성 요소를 감싸기 때문에 value prop은 useContext 후크를 통해서만 사용할 수 있기 때문에 가능합니다. React는 전 세계적으로 액세스할 수 있고 소비자 구성 요소에 의해 조작될 수 있는 모든 데이터를 관리하기 위해 기본적으로 제공하는 또 다른 멋진 도구입니다.

./src/comComponents/Navbar.jsx

export default function Navbar() {
 const [name, setName] = useContext(Auth);
 console.log(name)

 return (...)};

Navbar() 기능 구성 요소에서 더 이상 소품을 허용하지 않는 방법에 주목하세요. 대신 useContext(Auth)를 사용하여 Auth 컨텍스트를 소비하고 name과 setName을 모두 가져옵니다. 이는 또한 더 이상 Navbar에 prop을 전달할 필요가 없다는 것을 의미합니다.

./src/App.jsx

// ... //
return (
   <BrowserRouter>
     <div className="h-screen">
       <Navbar/> // no need to pass prop anymore
// ... //

인증 컨텍스트 업데이트

제공된 setName 함수를 사용하여 “name” 상태 변수를 조작할 수도 있습니다.

./src/pages/EditUser.jsx

export default function EditUser() { // no need to accept props anymore
 const [name, setName] = useContext(Auth); // grabbing the name and setName variables from Auth context
 console.log("Rendering: EditUser");

useReducer() 후크 소개

이전 예에서는 컨텍스트 API를 사용하여 구성 요소 트리에서 상태를 관리하고 공유했습니다. 여전히 useState를 논리의 기반으로 사용하고 있으며 상태가 여전히 단순한 객체이므로 대부분 괜찮습니다. 하지만 이 시점에서는 인증 흐름의 기능을 확장하려면 현재 로그인한 사용자의 이메일보다 더 많은 정보를 저장해야 합니다. 여기서 이전에 적용했던 제한 사항으로 되돌아갑니다. 복잡한 상태와 함께 useState를 사용하면 다행스럽게도 React는 복잡한 상태를 관리하기 위해 useState에 대한 대안(useReducer 입력)을 제공하여 이 문제를 해결합니다.

useReducer는 useState의 일반화된 버전으로 생각할 수 있으며 두 개의 매개변수, 즉 감속기 함수와 초기 상태를 사용합니다.

초기 상태에 대해 주목할 만한 흥미로운 사항은 없습니다. 마법은 리듀서 함수 내에서 발생합니다. 발생한 작업 유형을 확인하고 해당 동작에 따라 리듀서는 상태에 적용할 업데이트를 결정하고 새 값을 반환합니다. .

아래 코드를 살펴보면 감속기 함수에는 두 가지 가능한 작업 유형이 있습니다.

  • LOGIN”: 이 경우 사용자 상태는 작업 페이로드 내에 제공된 새 사용자 자격 증명으로 업데이트됩니다.

  • LOGOUT”: 이 경우 사용자 상태는 로컬 저장소에서 제거되고 다시 null로 설정됩니다.

작업 개체에는 적용할 논리를 결정하는 유형 필드와 해당 논리를 적용하는 데 필요한 데이터를 제공하는 선택적 페이로드 필드가 모두 포함되어 있다는 점에 유의하는 것이 중요합니다.

마지막으로 useReducer 후크는 현재 상태와 리듀서에 작업을 전달하는 데 사용하는 디스패치 함수를 반환합니다.

나머지 논리는 이전 예제와 동일합니다.

./src/contexts/Auth.jsx

import { createContext, useEffect, useReducer, useState } from "react";

export const Auth = createContext();
import { createContext, useReducer } from "react";

export const Auth = createContext();
const reducer = (state, action) => {
  switch (action.type) {
    case "LOGIN":
      return { user: action.payload };

    case "LOGOUT":
      localStorage.removeItem("user");
      return { user: null };

    default:
      break;
  }
};

export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, {
    user: null,
  });

  return (
    <Auth.Provider value={{ ...state, dispatch }}>{children}</Auth.Provider>
  );
};

setState setter 함수를 사용하는 대신 액션 전달 – 예: setName –

방금 언급했듯이 디스패치 기능을 사용하여 리듀서에 작업을 전달합니다. 다음 코드에서는 LOGIN 작업을 시작하고 사용자 이메일을 페이로드로 제공합니다. 이제 사용자 상태가 업데이트되고 이 변경 사항이 트리거됩니다. 사용자 상태를 구독하는 모든 구성 요소를 다시 렌더링합니다. 리렌더링은 상태의 실제 변경이 발생한 경우에만 트리거되고 리듀서가 동일한 이전 상태를 반환하는 경우에는 다시 렌더링되지 않는다는 점을 지적하는 것이 중요합니다.

export default function Login() {
 const { dispatch } = useContext(Auth);


 const handleLogin = async (e) => {
   // Updating the global Auth context
   dispatch({ type: "LOGIN", payload: {email: email.current.value} });
 };

 return (...)};

사용자 로그인

JWT Local storage

전문가 팁

성공적인 로그인 후 수신한 사용자 개체가 이제 localStorage에 어떻게 저장되는지 확인하세요.

로그인을 위한 사용자 정의 후크

이제 useReducer를 잘 이해했으므로 로그인 및 로그아웃 논리를 별도의 사용자 정의 후크로 캡슐화할 수 있습니다. 로그인 후크에 대한 단일 호출을 통해 로그인 경로에 대한 API 호출을 처리하고 새 사용자를 검색할 수 있습니다. 자격 증명을 로컬 저장소에 저장하고 LOGIN 호출을 전달하여 사용자 상태를 업데이트하는 동시에 오류 처리를 처리합니다.

./src/hooks/useLogin.jsx

import { useContext, useState } from "react";
import { Auth } from "../contexts/Auth";

export const useLogin = () => {
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  const { dispatch } = useContext(Auth);

  const login = async (email, password) => {
    setIsLoading(true);
    setError(null);

    try {
      const response = await fetch("/api/users/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, password }),
      });

      const json = await response.json();

      if (json.name === "Error") {
        setError(json.message);
        setIsLoading(false);
      }

      if (!response.ok) {
        setIsLoading(false);
        setError(json);
      }

      if (response.ok) {
        // Save the user and token in the localstorage
        localStorage.setItem("user", JSON.stringify(json));

        // Updating the global Auth context
        dispatch({ type: "LOGIN", payload: json });

        setIsLoading(false);
      }
    } catch (error) {
      console.log(error);
    }
  };

  return { error, isLoading, login };
};

참고: 독자 중 고급 React 사용자의 경우 사용자 자격 증명을 검색하기 위해 useReducer의 지연 초기화 기능을 사용하지 않은 이유가 궁금할 수 있습니다. useReducer는 init 함수라는 세 번째 선택적 매개 변수를 허용합니다. 이 함수는 다음과 같은 경우에 사용됩니다. 상태의 초기 값을 얻으려면 먼저 몇 가지 논리를 적용해야 합니다. 이를 선택하지 않은 이유는 단순한 문제 분리 문제입니다. 이 방식의 코드는 이해하기 쉽고 결과적으로 유지 관리도 더 간단합니다. .

로그인 페이지

다음은 useLogin() 후크를 사용하여 로그인 기능을 추출하고 사용자가 제출한 자격 증명으로 로그인 기능을 호출한 후 로그인 페이지의 상단 부분이 어떻게 보이는지 보여줍니다.

// ... ... //
export default function Login() {
 const { login, isLoading, error } = useLogin();
 console.log("Rendering: Login");
 const email = createRef(null);
 const password = createRef(null);

 const handleLogin = async (e) => {
   await login(email.current.value, password.current.value);
 };
 return (...)
// ... ... //

또한 사용자가 양식을 제출할 때 제출 기능을 비활성화합니다.

<button
  onClick={handleLogin}
  disabled={isLoading}
  className="btn btn-square w-full bg-gray-100 text-gray-600 hover:bg-gray-300 border-none"
>
  {isLoading && "A moment please!"}
  {!isLoading && "Login"}
</button>

그리고 백엔드에서 받은 인증 오류를 렌더링합니다.

{
  error && <span className="text-red-500 p-2">{error.message}</span>;
}

Rendering Errors

사용자 상태 유지

사용자 개체를 localStorage에 저장해야 하는 이유가 궁금할 수 있습니다. 간단히 말해서 토큰이 만료되지 않는 한 사용자를 로그인 상태로 유지하고 싶습니다. localStorage를 사용하는 것은 우리의 예에서처럼 JSON 비트를 저장하는 좋은 방법입니다. 로그인한 후 페이지를 새로 고치면 상태가 어떻게 지워지는지 확인하세요. 이 문제는 useEffect 후크를 사용하여 localStorage에 저장된 사용자 개체가 있는지 확인하여 쉽게 해결할 수 있으며, 있는 경우 자동으로 사용자를 로그인합니다.

// ... ... //
export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, {
    user: null,
  });

  useEffect(() => {
    const user = JSON.parse(localStorage.getItem("user"));

    if (user) {
      return dispatch({ type: "LOGIN", payload: user });
    }
  }, []);

  return (
    <Auth.Provider value={{ ...state, dispatch }}>{children}</Auth.Provider>
  );
};

로그아웃을 위한 사용자 정의 후크

로그아웃 후크에도 동일한 내용이 적용됩니다. 여기서는 상태 및 로컬 저장소 모두에서 현재 사용자 자격 증명을 제거하기 위해 LOGOUT 작업을 전달합니다.

./src/hooks/useLogout.jsx

import { useContext } from "react";
import { Auth } from "../contexts/Auth";

export const useLogout = () => {
  const { dispatch } = useContext(Auth);

  const logout = () => {
    // delete user from the localstorage
    localStorage.removeItem("user");
    // Wipe out the Auth context (user:null) / dipatch 'LOGOUT'
    dispatch({ type: "LOGOUT" });
  };

  return { logout };
};

사용자 로그아웃

사용자를 로그아웃하려면 UserDropdown.jsx에 있는 로그아웃 버튼에 클릭 이벤트를 추가하고 그에 따라 처리해 보겠습니다.

./src/comComponents/UserDropdown.jsx

// Extracting the logout function from useLogout() and handling the click event listener //
export default function UserDropdown() {
 const { user } = useContext(Auth);
 const { logout } = useLogout();
 console.log("Rendering: UserDropdown");

 const handleLogout = () => {
   logout();
 };
// ... ... //

// Adding a click event listener to logout button //
<li>
   <button onClick={handleLogout}>Logout</button>
</li>
// ... ... //

User Logout

React 경로 보호

애플리케이션 구현의 마지막 단계는 전역 사용자 상태를 활용하여 사용자 탐색을 제어하는 ​​것입니다. 이는 우리가 달성해야 하는 동작에 대한 빠른 알림입니다. 처음에는 사용자에게 로그인 페이지가 표시되고, 그 시점부터 사용자는 홈 페이지에만 액세스할 수 있습니다. 로그인에 성공한 후 마찬가지로 사용자는 로그아웃 시 로그인 페이지로 리디렉션됩니다.

우리는 ”/” 및 “/login”이라는 2개의 경로를 정의하여 React-router-dom 라이브러리의 도움으로 이를 달성합니다. 전역 인증 상태를 사용하여 각 경로에서 렌더링할 구성 요소를 제어합니다., null로 평가되는 인증은 인증되지 않은 사용자를 나타내고 그 반대의 경우도 마찬가지입니다.

./src/App.jsx

import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import "./App.css";
import Navbar from "./components/Navbar";
import Home from "./pages/Home";
import Login from "./pages/Login";
import { useContext } from "react";
import { Auth } from "./contexts/Auth";

function App() {
  const { user } = useContext(Auth);
  return (
    <BrowserRouter>
      <div className="h-screen">
        <Navbar />
        <main className="px-4">
          <Routes>
            <Route
              path="/"
              element={user ? <Home /> : <Navigate to="/login" />}
            />
            <Route
              path="/login"
              element={!user ? <Login /> : <Navigate to="/" />}
            />
          </Routes>
        </main>
      </div>
    </BrowserRouter>
  );
}

export default App;

요약 다이어그램

Diagram

마무리

이 기사에서 우리는 인증 워크플로를 위한 상태 관리를 구현하는 간단하지만 매우 일반적인 사용 사례를 다양한 접근 방식, 각 접근 방식의 근거 및 장단점을 통해 다루려고 했습니다. 클라이언트 측 프레임워크, 특히 React의 상태 관리는 프론트엔드 커뮤니티에서 가장 많이 논의되는 주제 중 하나입니다. 애플리케이션의 성능과 확장성을 성패시킬 수 있기 때문입니다. 이 상태 관리 문제를 해결하기 위해 시도하는 다양한 기술, 패턴, 라이브러리 및 도구의 양은 압도적입니다. 우리의 목표는 사용자가 자신의 애플리케이션에서 이를 구현할 수 있도록 관행에 대한 확실한 이해를 제공하는 것이었습니다. 더 복잡한 애플리케이션의 상태 관리 기술과 패턴에 대해서는 React에서 확장 가능한 상태 관리를 위해 redux에 대해 알아보는 다음 기사를 확인하세요.

곧 출시됩니다

ContextAPI와 Redux 툴킷 비교

Redux는 Redux 툴킷 이전에 커뮤니티에서 수년 동안 채택되었습니다. 실제로 RTK는 새로운 애플리케이션에서 redux 상태 관리를 부트스트랩하기 위한 시작 템플릿으로 도입되었습니다(초기 이름은 2019년 10월에 “redux-starter-kit”였습니다). 오늘날 Redux 관리자와 커뮤니티 사이에는 Redux 툴킷이 redux로 작업하는 유효한 방법이라는 일반적인 합의가 있습니다. RTX는 많은 Redux 로직을 추상화하여 훨씬 덜 장황하고 사용하기 쉽게 하며 개발자가 다음을 수행하도록 권장합니다. 모범 사례를 따르세요. 둘 사이의 주요 차이점 중 하나는 Redux가 독립적으로 구축되어 최소한의 API를 제공하고 개발자가 일반적인 작업을 위한 자체 라이브러리를 작성하고 코드 구조를 처리하여 대부분의 무거운 작업을 수행할 것으로 기대한다는 것입니다. 이로 인해 개발 시간이 느려지고 코드가 지저분해졌습니다. Redux 툴킷은 개발자가 일반적인 함정에 빠지는 것을 방지하는 추상화의 추가 계층으로 추가되었습니다. 더 많은 통찰력과 관리자의 추론을 보려면 공식 문서를 참조하세요. 여기.