<React> useReducer, contextAPI, useContext

·2023년 6월 19일
0

React

목록 보기
8/23

useReducer

useReducer에 객체 속성을 종속성으로 추가하기

객체 destructuring을 통하여 객체 속성을 의존성 배열에 추가할 수 있다.
이는 매우 일반적인 패턴 및 접근 방식이다.

핵심은 destructuring을 사용한다는 것이 아니라, 전체 객체 속성을 사용하는 것이 아닌, 특정 속성만 종속성으로 적용하는 것이다.

만약 객체 디스트럭처링을 하지 않고 특정 객체만 의존성 배열에 넣어버리면
해당 객체 속성 중에 변경사항이 있을 때, 계속해서 useEffect가 불필요하게 재실행될 것이다.

아래 의존성 배열에 있는 emailIsValid, passwordIsValid 데이터는 객체를 구조분해하여 isValid라는 속성만을 추출해낸 것.
// 객체 디스트럭처링, 별칭 할당, isValid 속성에만 접근.
// 매우 중요한 기술임. useEffect를 더욱 최적화할 수 있는 기술. 이펙트가 불필요하게 실행되는 것을 피하기 위해서.
// 다른 사용 사례 : 이펙트 의존성으로 prop이 있는 경우. 어떤 프롭이 있는 경우에만 실행되게끔 할 때 사용

// 리듀서 함수
const [emailState, dispatchEmail] = useReducer(emailReducer, {
    value: "",
    isValid: null,
  });
  const { isValid: emailIsValid } = emailState;
  const { isValid: passwordIsValid } = passwordState;

// useEffect를 활용한 디바운싱 구현
  useEffect(() => {
    const identifier = setTimeout(() => {
      console.log("checking form");
      setFormIsValid(emailIsValid && passwordIsValid);
    }, 1000);

    // clean up 함수는 첫 번째 사이드 이펙트가 실행되기 전에는 실행되지 않는다.
    return () => {
      console.log("Clean UP");
      clearTimeout(identifier);
    };
  }, [emailIsValid, passwordIsValid]);

 const emailChangeHandler = (event) => {
    dispatchEmail({ type: "USER_INPUT", val: event.target.value });

    setFormIsValid(emailState.value.includes("@") && passwordState.isValid);
  };

  const passwordChangeHandler = (event) => {
    dispatchPassword({ type: "USER_PASSWORD", val: event.target.value });

    setFormIsValid(passwordState.value && event.target.value.trim().length > 6);
  };

객체 디스트럭처링 (구조 분해 할당)

객체의 특정 속성에 접근: 객체 디스트럭처링을 사용하면 객체의 특정 속성에 간편하게 접근할 수 있음.

예를 들어, const { name, age } = person;와 같이 사용하여 person 객체의 name과 age 속성에 각각 접근할 수 있다.

변수 이름 변경: 객체 디스트럭처링을 사용하여 객체 속성의 값을 새로운 변수에 할당할 때, 동시에 변수 이름을 변경할 수 있다. 예를 들어, const { isValid: emailIsValid } = emailState;와 같이 사용하여 emailState 객체의 isValid 속성 값을 emailIsValid 변수에 할당하고 있음.

기본값 설정: 객체 디스트럭처링을 사용하여 속성의 기본값을 설정할 수 있다. 속성이 존재하지 않을 경우 기본값이 변수에 할당된다.

예를 들어, const { name = 'Unknown', age = 0 } = person;과 같이 사용하여 person 객체의 name 속성이 존재하지 않을 경우 기본값으로 'Unknown'을, age 속성이 존재하지 않을 경우 기본값으로 0을 변수에 할당할 수 있다.

중첩된 객체 디스트럭처링: 객체 내부에 중첩된 객체가 있는 경우, 중첩된 객체의 속성에도 동일한 방식으로 접근할 수 있다. 예를 들어, const { address: { city, country } } = person;와 같이 사용하여 person 객체의 address 속성 내부의 city와 country 속성에 각각 접근할 수 있습니다.

객체 디스트럭처링은 코드를 더 간결하고 가독성 있게 만들어주는 장점이 있다.
특히, 객체에서 필요한 속성만을 추출하여 사용할 때 유용하다.

예시코드
// 객체 디스트럭처링, 별칭 할당, isValid 속성에만 접근.
// 매우 중요한 기술임. useEffect를 더욱 최적화할 수 있는 기술. 이펙트가 불필요하게 실행되는 것을 피하기 위해서.
// 다른 사용 사례 : 이펙트 의존성으로 prop이 있는 경우.
//어떤 prop이 있는 경우에만 실행되게끔 할 때 사용

// emailState 객체의 isValid 속성에만 접근하고 싶을 때.
// 이로써 isValid 속성에 emailIsValid라는 별칭을 달아줌으로써 접근 가능하다.
  const { isValid: emailIsValid } = emailState;
  const { isValid: passwordIsValid } = passwordState;

useState() vs useReducer()

언제 사용해야하는가?

useState

  • 주요 state 관리 Hook
  • 개별 state 및 데이터를 다루기에 적합.
  • 간단한 state에 적합하고 업데이트가 간편.
  • 관리해야 하는 데이터가 몇 안되는 경우에 사용한다면 적합하다.

즉, state 변경 경우가 다양하지 않을 때 사용하면 적합하다.

state가 객체 등의 타입이 아니라 단순할 때 적합하다.

useReducer

useState는 간단한 데이터 타입을 다루기에 적합하다.
하지만 다루는 state가 객체나 복잡한 경우라면?
useReducer를 사용하기에 적합하다.

  • 복잡한 state나 객체 타입에 useReducer를 사용하는 것이 좋다.
  • 일반적으로 useReducer는 Reducer 함수를 사용함으로써 복잡한 state 업데이트를 할 수 있도록 효과적인 방법을 제공해준다.
  • 최신 state 스냅샷과 복잡할 수도 있는 로직을 컴포넌트 함수 body에서 별도의 Reducer 함수로 이동시킬 수도 있다.
  • 특히 연관된 state 조각들로 구성된 state 관련 데이터를 다루는 경우에 사용.
    - 예시로 Form input state에 useReducer가 도움될 수 있다,

React contextAPI

App이 커질수록 Props Drilling 단계가 깊어질 수 있다.
State를 단순 전달하기만 하는 Props Drilling이 깊어지는 현상을 막고, 실제로 필요한 데이터를 부모로부터 받는 컴포넌트에서만 사용할 수 있으면 좋을 것이다.

무조건 부모를 통해 데이터를 전달받지 않도록. 부모는 데이터를 관리하지도, 필요하지도 않을 수 있다. 그저 props 전달용으로 사용되지 않기 위해서.

이를 위해서 컴포넌트 전체에서 사용할 수 있는 내부적인 React state 저장소가 있는데, React Context라는 개념이다.

예시로 React Context를 이용하면 해당 컴포넌트 전체에서 Action을 트리거할 수 있다. 그리고 관련된 컴포넌트에 직접 전달할 수 있게 된다.

즉, 깊은 Prop Chain을 만들지 않고도 상태를 전달가능하게 한다.

Provider(제공자)와 Consumer(소비자)가 있다.

auth-context.js

// 케밥 표기법으로 파일명을 명시.

import React from "react";

// 컨텍스트 객체 생성. 기본 컨텍스트를 만든다.
const AuthContext = React.createContext({
  isLoggedIn: false,
});

// 다른 파일에서 사용할 수 있도록 내보내기(export)
export default AuthContext;
app.js

// .. 기타 코드
import AuthContext from "./components/store/auth-context";

function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    const storedUserLoggedInInformation = localStorage.getItem("isLoggedIn");

    if (storedUserLoggedInInformation === "1") {
      setIsLoggedIn(true);
    }
  }, []);

  const loginHandler = (email, password) => {
    setIsLoggedIn(true);
  };

  const logoutHandler = () => {
    setIsLoggedIn(false);
  };


  return (
    // 여기서 auth-context.js를 불러와서 상태를 app.js 컴포넌트 아래에 있는 컴포넌트들에게 접근가능하게 한다.
    <AuthContext.Provider
      value={{
        isLoggedIn: isLoggedIn,
      }}
    >
      <MainHeader onLogout={logoutHandler} />
      <main>
        {!isLoggedIn && <Login onLogin={loginHandler} />}
        {isLoggedIn && <Home onLogout={logoutHandler} />}
      </main>
    </AuthContext.Provider>
  );
}

export default App;
Navigation.js
// MainHeader 컴포넌트에 포함되어있는 하위 컴포넌트

import React from "react";
import AuthContext from "../store/auth-context";

import classes from "./Navigation.module.css";

const Navigation = (props) => {
  return (
    // Provider로 뿌려준 상태를 Consumer로 받아서 콜백함수를 이용하여 사용. ctx로 접근하는 모습.
    <AuthContext.Consumer>
      {(ctx) => {
        return (
          <nav className={classes.nav}>
            <ul>
              {ctx.isLoggedIn && (
                <li>
                  <a href="/">Users</a>
                </li>
              )}
              {ctx.isLoggedIn && (
                <li>
                  <a href="/">Admin</a>
                </li>
              )}
              {ctx.isLoggedIn && (
                <li>
                  <button onClick={props.onLogout}>Logout</button>
                </li>
              )}
            </ul>
          </nav>
        );
      }}
    </AuthContext.Consumer>
  );
};

export default Navigation;

그러나 Provider.Consumer로 불러와 사용하는 방법이 아닌, useContext() Hook으로 사용하는 방법이 있다.

useContext()로 ContextAPI를 사용한 코드

React Hook 중에 하나인 useContext()로 <Provider.Consumer>를 대체한 코드.

import React, { useContext } from "react"; // useContext import
import AuthContext from "../store/auth-context";

import classes from "./Navigation.module.css";

const Navigation = (props) => {
  // 상수에 useContext() Hook을 저장. 인자로 사용할 ContextAPI를 넣어준다.
  const ctx = useContext(AuthContext);

  return (
    <nav className={classes.nav}>
      <ul>
    // 상수에 접근하여 컨텍스트 데이터를 사용하는 모습.
        {ctx.isLoggedIn && (
          <li>
            <a href="/">Users</a>
          </li>
        )}
        {ctx.isLoggedIn && (
          <li>
            <a href="/">Admin</a>
          </li>
        )}
        {ctx.isLoggedIn && (
          <li>
            <button onClick={props.onLogout}>Logout</button>
          </li>
        )}
      </ul>
    </nav>
  );
};

export default Navigation;

<Provider.Consumer> 보다 훨씬 보기 좋은 코드로 같은 기능 구현이 가능하다.

많은 컴포넌트를 통해 전달하고자 하는 것이 많을 경우에만, 예시로 navigation 같은 특정적일 일을 하는 컴포넌트들이 많은 곳으로 전달하는 경우에는 컨텍스트를 사용하는 것이 좋다.

컨텍스트를 굳이 사용하지 않아도 되지만, 사용함으로써 얻을 수 있는 장점은 하나의 파일에서 상태를 관리함으로써 App Component가 더 간결해지는 것이다.

React Context 제한

Context가 컴포넌트 구성을 대체할 수는 없음.

앱 전체 또는 컴포넌트 전체 state에는 context가 적합할 수 있다.
(== 기본적으로 여러 컴포넌트에 영향을 미치는 State들에게는)

  1. React 변경이 잦은 경우 Context는 썩 적합하지 않음.
    (매초 또는 1초에 여러번 state가 변경되는 경우.)
    이런 경우에 Context를 사용하는 것은 좋지 않다. 라고 React 개발팀에서 공식적으로 의견을 남겼음.
    sebmarkbage_comment

  2. Redux를 사용하여 state가 자주 변경되는 상황에서 효과적으로 대응할 수 있다.

0개의 댓글