[study] useState와 전역상태관리

김대운·2023년 4월 11일
0

study

목록 보기
1/7
post-thumbnail

📙 useState란 ??

React에서 useState는 함수 컴포넌트에서 상태(state)를 관리할 때 사용되는 Hook 중 하나입니다.
useState를 사용하면 함수 컴포넌트에서도 상태를 간편하게 관리할 수 있습니다.

useState는 배열을 반환하며, 첫 번째 요소는 현재 상태 값(current state)이고, 두 번째 요소는 상태를 업데이트하는 함수(update function)입니다. useState를 사용하면 다음과 같이 상태를 생성할 수 있습니다.

import React, { useState } from 'react';

function Counter() {
  // count 상태를 생성하고 기본값으로 0을 지정합니다.
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

위 예제에서 useState를 사용하여 count라는 상태를 생성하고, 기본값으로 0을 지정합니다. 상태를 업데이트하는 함수는 setCount입니다. 이 함수는 현재 상태 값을 변경하는 함수이며, 변경된 값을 사용하여 새로운 상태를 생성합니다.

위 코드에서는 버튼을 클릭할 때마다 count 상태 값을 1씩 증가시킵니다. setCount 함수를 호출하면 React는 자동으로 컴포넌트를 다시 렌더링하며, 변경된 count 값을 표시합니다.

useState는 상태를 변경할 때마다 컴포넌트를 다시 렌더링하기 때문에, 상태가 변경되는 빈도가 높은 경우에는 성능 이슈가 발생할 수 있습니다. 이런 경우에는 useMemo나 useCallback과 같은 다른 Hook을 사용하여 최적화할 수 있습니다.

📗 그럼 Hook이란 ??

React에서 Hook은 함수 컴포넌트에서 상태(state)를 관리하거나 부작용(side effect)을 처리하는 함수를 의미합니다. Hook은 React 버전 16.8 이상부터 도입되었습니다.

side effect 란 ?
React에서 side effect란 컴포넌트가 렌더링될 때 발생하는 작업으로, 주로 다음과 같은 작업을 의미합니다.

  • API 요청
  • DOM 조작
  • 이벤트 핸들링
  • 타이머 설정
  • 외부 라이브러리 사용
  • 컴포넌트에서 관리되지 않는 상태 값에 접근

즉, side effect는 컴포넌트의 상태나 프로퍼티 값 등이 변경되어 화면이 업데이트되는 과정에서 발생하는 작업을 의미합니다. React에서는 이러한 side effect를 처리하기 위해 useEffect Hook을 사용합니다.

useEffect Hook을 사용하면 컴포넌트가 렌더링될 때마다 실행되는 함수를 정의할 수 있습니다. 이 함수는 컴포넌트가 처음 마운트될 때, 업데이트될 때, 언마운트될 때 등 다양한 상황에서 실행될 수 있습니다.

함수 컴포넌트는 상태 값을 관리할 수 없었기 때문에, Hook이 도입되기 전에는 상태 값을 관리하기 위해 클래스 컴포넌트를 사용해야 했습니다. 하지만 Hook이 도입되면서 함수 컴포넌트에서도 상태 값을 간편하게 관리할 수 있게 되었습니다.

Hook은 다음과 같은 기능을 제공합니다.

  • useState: 상태를 관리할 때 사용합니다.
  • useEffect: 부작용(side effect)을 처리할 때 사용합니다. (ex. API 요청, DOM 조작, 이벤트 핸들링 등)
  • useContext: Context를 사용할 때 사용합니다.
  • useReducer: 복잡한 상태 관리 로직을 구현할 때 사용합니다.
  • useCallback, useMemo: 성능 최적화를 위해 사용합니다.
  • useRef: DOM 요소나 변수를 관리할 때 사용합니다.
  • useLayoutEffect, useImperativeHandle, useDebugValue 등
    각 Hook은 특정 기능을 제공하며, 함수 컴포넌트에서 필요한 Hook을 사용하여 상태 값을 관리하거나 부작용을 처리할 수 있습니다. 이러한 Hook을 사용하여 함수 컴포넌트를 작성하면, 클래스 컴포넌트와 마찬가지로 React의 생명주기 메서드를 대체할 수 있습니다.

📗 그럼 useState는 어떤 원리로 작동할까 ?

React의 useState Hook은 클로저를 사용하여 상태 값을 관리합니다. useState는 컴포넌트가 처음으로 렌더링될 때 한 번만 호출되며, 그 때 상태 값을 초기화합니다. 그리고 상태 값을 변경할 때마다 해당 함수가 재실행되며, 이 때 이전 상태 값을 유지하기 위해 클로저를 사용합니다.

클로저란?
클로저(Closure)는 함수와 함수가 선언될 때의 렉시컬 환경(Lexical Environment)과의 조합입니다. 클로저는 함수가 선언될 때 해당 함수 내에서 참조되는 변수들을, 그 변수들이 선언된 함수의 렉시컬 환경과 함께 기억하여, 함수가 실행될 때에도 접근할 수 있도록 합니다.

간단한 예제를 통해 설명하자면, 다음과 같이 함수가 중첩되어 있는 경우를 생각해보겠습니다.

function outer() {
  const outerVar = "I am outer variable";
  function inner() {
    const innerVar = "I am inner variable";
    console.log(outerVar, innerVar);
  }
  inner();
}

위 코드에서는 outer 함수 내부에 inner 함수가 정의되어 있습니다. inner 함수 내부에서는 outer 함수 내부의 outerVar 변수와 innerVar 변수를 참조하고 있습니다. 이때 inner 함수가 outer 함수 내부에서 정의되었기 때문에, inner 함수는 outer 함수 내부의 변수에 접근할 수 있습니다.

이와 같은 동작 방식을 클로저라고 합니다. 클로저는 자바스크립트에서 중요한 개념 중 하나로, 함수형 프로그래밍에서는 불변성을 유지하기 위한 상태 관리 방법으로 많이 사용됩니다. 클로저를 사용하면, 함수 내부에서 선언된 변수를 외부에서 직접적으로 변경할 수 없기 때문에, 안전하게 상태를 관리할 수 있습니다.

클로저를 이용한 상태 관리 예제 코드

function createCounter() {
  let count = 0; // 상태값

  return function () {
    count += 1; // 상태값 변경
    console.log(`Clicked ${count} times`);
  };
}

const counter = createCounter(); // 클로저 함수

counter(); // "Clicked 1 times"
counter(); // "Clicked 2 times"
counter(); // "Clicked 3 times"

위 코드에서 createCounter 함수는 내부에 상태값 count를 선언하고, 클로저 함수를 반환합니다. 클로저 함수 내부에서 count 값이 변경되며, 클로저 함수가 호출될 때마다 변경된 값을 출력합니다.

이와 같이 클로저를 이용하여 상태값을 저장하고, 상태값을 변경하는 함수를 반환함으로써, useState 훅이 상태값을 관리하는 원리를 구현할 수 있습니다.

📙 전역상태관리란 ??

전역 상태 관리란, 애플리케이션 내에서 여러 컴포넌트에서 공유하는 상태를 중앙에서 관리하는 것을 의미합니다. 일반적으로 React 애플리케이션에서는 Redux, MobX, Context API, Recoil, zustand 등의 라이브러리를 사용하여 전역 상태를 관리합니다.

전역 상태 관리를 사용하면 여러 컴포넌트에서 공유하는 상태를 중앙에서 관리할 수 있기 때문에, 상태 업데이트 로직을 중앙에서 관리할 수 있습니다. 또한, 상태 변경에 따른 컴포넌트 렌더링이 필요한 경우에도, 중앙에서 상태 변경을 감지하여 필요한 컴포넌트만 다시 렌더링할 수 있습니다.

하지만, 전역 상태 관리를 사용하면, 상태 업데이트 로직이 복잡해지거나, 성능 문제가 발생할 수 있습니다. 따라서, 전역 상태 관리를 사용할 때는 상황에 맞게 적절한 라이브러리를 선택하고, 사용하는 것이 중요합니다.

useState로 상태관리를 하면 되는데 전역상태관리는 왜 생겼을까 ?

React에서 제공하는 useState 훅을 사용하여 컴포넌트 내에서 상태를 관리할 수 있지만, 애플리케이션 규모가 커지면서 컴포넌트 구조가 복잡해지면 상태 업데이트 로직이 복잡해지고, 여러 컴포넌트에서 공유하는 상태를 효율적으로 관리하기 어려워집니다.

이러한 상황에서 전역 상태 관리를 사용하면, 여러 컴포넌트에서 공유하는 상태를 중앙에서 효율적으로 관리할 수 있습니다. 또한, 상태 변경에 따른 컴포넌트 렌더링이 필요한 경우에도, 중앙에서 상태 변경을 감지하여 필요한 컴포넌트만 다시 렌더링할 수 있습니다.

또한, 전역 상태 관리를 사용하면 여러 컴포넌트에서 중복되는 로직을 효율적으로 관리할 수 있습니다. 예를 들어, 로그인 상태를 관리하는 로직이 여러 컴포넌트에서 중복되어 있다면, 전역 상태 관리를 사용하여 중앙에서 로그인 상태를 관리하면, 로그인 상태를 변경할 때마다 여러 컴포넌트에서 로그인 상태를 업데이트하는 로직을 모두 수정할 필요 없이, 중앙에서 한 번만 수정하면 됩니다.

따라서, 전역 상태 관리는 큰 규모의 애플리케이션에서 상태 관리를 효율적으로 처리하기 위해 사용됩니다.

전역상태관리도 클로져를 활용한다.

Redux 라이브러리는 전역 상태 관리를 위해 Redux Store라는 단 하나의 상태 저장소를 만들고, 해당 저장소의 상태를 업데이트하는 함수인 reducer를 사용합니다. reducer 함수는 이전 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수입니다. 이 때 reducer 함수는 클로저를 활용하여 상태를 안전하게 유지합니다.

또 다른 예로는 MobX 라이브러리가 있습니다. MobX는 observable이라는 데코레이터 함수를 사용하여 상태를 관찰 가능한 객체로 만들고, 해당 객체의 상태가 변경될 때마다 자동으로 리렌더링하는 방식으로 전역 상태 관리를 구현합니다. 이 때도 observable 함수가 클로저를 사용하여 상태를 유지합니다.

useState와의 차이점

useState는 컴포넌트 내부에서 로컬 상태를 관리하기 위해 사용됩니다. 따라서 useState가 생성한 클로저는 해당 컴포넌트 함수 내부에서만 유효합니다.

반면에 전역상태관리 라이브러리(예: Redux, MobX, Recoil)는 애플리케이션 전체에서 사용되는 전역 상태를 관리하기 위해 사용됩니다. 이 경우, 애플리케이션 전역에서 상태를 안전하게 보호하기 위해 클로저를 사용합니다. 이를 통해 상태의 불변성을 보장하고, 컴포넌트 간에 데이터를 공유하며 상태를 업데이트할 수 있습니다.

더쉽게 설명하자면,

useState는 해당 컴포넌트의 로컬 상태를 관리하므로, 스코프의 범위는 해당 컴포넌트 내부입니다. 반면에 전역 상태 관리 라이브러리는 애플리케이션 전역에서 상태를 관리하므로, 스코프의 범위는 애플리케이션 전역이 됩니다. 따라서 전역 상태 관리 라이브러리는 다른 컴포넌트들과도 상호작용이 가능합니다.

// 전역 스코프
const globalVar = "I am global";

function foo() {
  // 함수 스코프
  const functionVar = "I am in foo";

  function inner() {
    // 중첩 함수 스코프
    const innerVar = "I am in inner";
    console.log(globalVar); // "I am global" 출력
    console.log(functionVar); // "I am in foo" 출력
    console.log(innerVar); // "I am in inner" 출력
  }

  inner();
}

foo();
console.log(globalVar); // "I am global" 출력
console.log(functionVar); // ReferenceError: functionVar is not defined 오류 발생
console.log(innerVar); // ReferenceError: innerVar is not defined 오류 발생

위 코드에서 globalVar는 전역 스코프에서 선언되었기 때문에 어디서든 접근할 수 있습니다. foo() 함수 내부에서 선언된 functionVar와 inner() 함수 내부에서 선언된 innerVar는 함수 스코프와 중첩 함수 스코프에서만 접근 가능합니다. console.log(functionVar)와 console.log(innerVar)에서는 각각 ReferenceError가 발생합니다.

0개의 댓글