[React] React Context

play·2022년 10월 27일
0

React

목록 보기
9/9
post-thumbnail

React Context

React 내부적으로 state를 관리할 수 있도록 도움

props 체인을 구축하지 않고도, 앱의 어떤 컴포넌트에서라도 직접 변경하여 직접 전달할 수 있게 해준다.


💡 장점

  • 리액트에 내장된 State 저장소인 React Context를 사용하면 긴 props 체인 만들지 않고도 컴포넌트 전체 state에 관해서 액션을 트리거할 수 있다.

  • context를 사용하면 중간에 있는 엘리먼트들에게 props를 넘겨주지 않아도 된다.

  • 전역적으로 사용되는 값을 관리할 수 있다. (함수, 상태, 라이브러리 인스턴스 등)

  • Redux는 '상태(state)'를 관리,
    / Context API는 React 컴포넌트들끼리 props로 정보를 주고 받지 않고 전역적으로 데이터를 가져다 쓸 수 있게 만든 툴


💡 단점 및 유의점

  • 컴포넌트 구성을 대체하는 건 불가능
  • 구성을 하려면 프롭을 사용하고, 컴포넌트 또는 전체 앱에서 state 관리하려면 컨텍스트를 사용하자.
  • state 변경이 잦은 경우엔 적합하지 않다.
    • 컴포넌트 전체에 state가 자주 변경되는 경우에도 context를 사용하고 싶은데, props는 적합하지 않는다면? → 리덕스 사용.
  • props는 여전히 컴포넌트 구성에서 필수적이고 중요하다. 모든 것에 context를 사용하려고 하지마라.


💡 사용 방법

React.createContext

const MyStore = React.createContext(defaultValue);
  • Context 객체를 생성한다.
  • 앱이나 컴포넌트 수준에선 그냥 텍스트일 수도 있으나 대부분엔 객체다.
  • createContext에서 얻는 결과는 컴포넌트가 되거나 컴포넌트를 포함하는 객체가 된다.
// 예제
// ./src/store/auth-context.js
const AuthContext = React.createContext({
  isLoggdIn: false,
});

export default AuthContext;

Context를 사용하기 위한 2가지 작업

1. Context.Provider : 공급

  • JSX 코드로 감싼다.
    • 감싸지지 않은 컴포넌트는 리스닝할 수 없다.
    • 만약 이 Context를 모든 곳에 쓰려면 App 컴포넌트에 있는 모든 것을 감싸줘야 함.
// ./src/App.js
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  return (
      <AuthContext.Provider>
        <MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} />
        <main>
          {!isLoggedIn && <Login onLogin={loginHandler} />}
          {isLoggedIn && <Home onLogout={logoutHandler} />}
        </main>
      </AuthContext.Provider>
  );
  • AuthContext 공급자는 컴포넌트다. JSX에서 사용 가능하다.
  • AuthContext를 Wrapper로 사용하므로 React.Fragment를 사용하지 않아도 된다.

2. Context.Consumer : 소비

2-1. Context.Consumer 사용

/* 2-1. 사용 예제
./src/componenets/Header/Navigation.js */
const Navigation = (props) => {
  return (
    <AuthContext.Consumer>
      {(ctx) => {
        return (
          <nav className={classes.nav}>
            <ul>
              {ctx.isLoggedIn && (
                <li>
                  <a href="/">Users</a>
                </li>
              )}
              {props.isLoggedIn && (
                <li>
                  <a href="/">Admin</a>
                </li>
              )}
              {props.isLoggedIn && (
                <li>
                  <button onClick={props.onLogout}>Logout</button>
                </li>
              )}
            </ul>
          </nav>
        );
      }}
    </AuthContext.Consumer>
  );
};
  • 데이터가 필요한 부분을 Context.Consumer로 감싸준다.
  • context의 자식은 중괄호 사이에 함수를 가진다.
  • 인수로 Context 데이터를 가져온다.
    • 이 예제에선 { isLoggdIn: false, } 가 된다.
  • 함수 안에서 JSX 코드를 반환한다.
    • 이제 props는 ctx로 바꿀 수 있다.

🧐 근데 저장하면 TypeError가 뜬다. 왜?

왜냐면 React.createContext에 기본값이 있지만, 이는 실제로 공급자 없이 소비하는 경우에만 사용되기 때문이다. 따라서 공급자는 필요하지 않음.

🧐 근데 공급자가 필요하다고 했잖아?

정확히 말하면 기본값이 있으면 공급자는 필요 없다.
그러나 실제론 변할 수 있는 값을 갖기 위해 공급자를 사용할 것이다.
따라서 충돌이 일어나지 않기 위해선 공급자 컴포넌트에서 프롭 값을 주자.

// ./src/App.js
  return (
    <AuthContext.Provider value={{ isLoggdIn: false }}>
      <MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} />
      <main>
        {!isLoggedIn && <Login onLogin={loginHandler} />}
        {isLoggedIn && <Home onLogout={logoutHandler} />}
      </main>
    </AuthContext.Provider>
  );
  • Provider 컴포넌트는 value라는 이름을 가져야한다.
    • 이유 : 우리가 만든 컴포넌트가 아니므로
    • 여기에 우리의 객체를 전달할 수 있다.
    • 하지만 이제 state나 앱 컴포넌트를 통해 해당 객체를 변경할 수 있다.
    • 변경될 때마다 새 값이 모든 소비 컴포넌트에 전달될 것이다.

이제 문제없이 로드된다.

근데 링크가 누락됨
→ 왜냐면 ctx.isLoggdIn에서 데이터를 가져오기 때문.

해결방안

// ./src/App.js
  return (
    <AuthContext.Provider 
    value={{ isLoggedIn: isLoggedIn }}> 
    // false에서 isLoggdIn으로 변경 
    
      <MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} />
      <main>
        {!isLoggedIn && <Login onLogin={loginHandler} />}
        {isLoggedIn && <Home onLogout={logoutHandler} />}
      </main>
    </AuthContext.Provider>
  );
  • 공급자에게 전달하는 값에서 isLoggedIn 값을 하드코딩하는 대신, isLoggedIn 변경한다.
  • 그러면 value 객체는 isLoggedIn 변경될 때마다 리액트에 의해 업데이트 된다.
  • 그리고 새로운 객체, 새로운 컨텍스트 객체는 모든 리스닝 컴포넌트로 전달된다.
  • 즉, 이 컨텍스트를 소비하는 모든 컴포넌트에 전달된다.

이전과 다른 점

  • 이를 전달하기 위해 props 사용할 필요가 없다.
  • 대신 공급자에 설정한다.
  • 그러면 모든 자식 컴포넌트의 모든 곳에서 리스닝할 수 있다.

그래서 isAuthenticated의 props 제거할 수 있다.

// ./src/App.js
  return (
    <AuthContext.Provider value={{ isLoggedIn: isLoggedIn }}>
      <MainHeader onLogout={logoutHandler} />
      <main>
        {!isLoggedIn && <Login onLogin={loginHandler} />}
        {isLoggedIn && <Home onLogout={logoutHandler} />}
      </main>
    </AuthContext.Provider>
  );

그리고 MainHeader에선 Navigation으로의 전달을 중단한다.

const MainHeader = (props) => {
  return (
    <header className={classes['main-header']}>
      <h1>A Typical Page</h1>
      <Navigation 
// isLoggedIn={props.isAuthenticated} 
onLogout={props.onLogout} />
    </header>
  );
};

이제 Navigation에서 context.isLoggedIn을 사용할 수 있다.
모든 props를 ctx로 바꿔주자.

// ./src/componenets/Header/Navigation.js 
const Navigation = () => {
  return (
    <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={ctx.onLogout}>Logout</button>
                </li>
              )}
            </ul>
          </nav>
        );
      }}
    </AuthContext.Consumer>
  );
};

다시 링크도 돌아왔다.
근데 함수에서 코드를 반환({(ctx) => { return (....)}}) 하는 건 별로라서 useContext hook을
사용하는 게 낫다.


2-2. useContext hook 사용 ← 일반적인 방법

// ./src/componenets/Header/Navigation.js 

import { useContext } from 'react'; // 추가

const Navigation = (props) => {
  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={ctx.onLogout}>Logout</button>
          </li>
        )}
      </ul>
    </nav>
  );
};

이렇게 하면 Context 값을 얻을 수 있다.




💡 정리

  • 대부분의 경우에선 props 사용하여 컴포넌트에 데이터를 전달한다. props는 컴포넌트를 구성하고 그것들을 재사용할 수 있는 매커니즘이기 때문이다.
  • 따라서 많은 컴포넌트를 통해 전달하는 경우 또는 특정한 일을 하는 경우(로그아웃, 네비게이션 등)에만 Context를 사용하는 게 좋다.
  • Context를 사용하면 더 간결한 코드를 작성할 수 있다.
  • 또한 앱 전체 state 관리가 조금 더 쉬워진다.



+ Context 개선

함수를 추가할 수 있다.

// ./src/store/auth-context.js
const AuthContext = React.createContext({
  isLoggdIn: false,
  onLogout: () => {}
});

export default AuthContext;
  • 더미 함수를 저장할 수 있다.
    • 사용 안하는 걸 굳이 왜? : IDE 자동완성 되기 때문. 뭐 안해도 상관 없음 근데 편할 것임

더 많은 로직을 가져오고 싶다면? : 선택사항

// ./src/store/auth-context.js
const AuthContext = React.createContext({
  isLoggdIn: false,
  onLogout: () => {}
});

// 추가 
export const AuthContextProvider = (props) => {
  return <AuthContext.Provider>{props.children}</AuthContext.Provider>;
};

export default AuthContext;

AuthContextProvider 컴포넌트를 추가해 props 받아와서 들어오는 모든 것을 전달하면 된다.
이렇게 하면 이제 useState를 임포트 할 수 있다.

// ./src/store/auth-context.js
import React, { useState } from "react";

const AuthContext = React.createContext({
  isLoggdIn: false,
  onLogout: () => {},
});

export const AuthContextProvider = (props) => {
  const [isLoggdIn, setIsLoggedIn] = useState(false);
  const logoutHandler = () => {
    setIsLoggedIn(false);
  };

  const loginHandler = () => {
    setIsLoggedIn(true);
  };

  return (
    <AuthContext.Provider
      value={{
        isLoggdIn: isLoggdIn,
        onLogout: logoutHandler,
      onLogin: (email, password) => {},

      }}
    >
      {props.children}
    </AuthContext.Provider>
  );
};
  • 이렇게 AuthContextProvider 컴포넌트에서 전체 로그인 State를 관리하여 모든 컨텍스트를 설정했다. === App 컴포넌트 간결
// ./src/App.js
function App() {
  const ctx = useContext(AuthContext);
  return (
    <React.Fragment>
      <MainHeader />
      <main>
        {!ctx.isLoggdIn && <Login />}
        {ctx.isLoggdIn && <Home />}
      </main>
    </React.Fragment>
  );
}
// ./src/store/auth-context.js
import React, { useState, useEffect } from "react";

const AuthContext = React.createContext({
  isLoggdIn: false,
  onLogout: () => {},
  onLogin: (email, password) => {},
});

export const AuthContextProvider = (props) => {
  const [isLoggdIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    const storedUserLoggedInInformation = localStorage.getItem("isLoggedIn");
    if (storedUserLoggedInInformation === "1") {
      setIsLoggedIn(true); 
    }
  }, []); 

  const logoutHandler = () => {
    localStorage.removeItem("isLoggedIn");
    setIsLoggedIn(false);
  };

  const loginHandler = () => {
    localStorage.setItem("isLoggedIn", "1"); 
    setIsLoggedIn(true);
  };

  return (
    <AuthContext.Provider
      value={{
        isLoggdIn: isLoggdIn,
        onLogout: logoutHandler,
        onLogin: loginHandler,
      }}
    >
      {props.children}
    </AuthContext.Provider>
  );
};

export default AuthContext;

장점

  • App 컴포넌트가 더 간결해진다.
  • App 컴포넌트는 이제 화면에 가져오는 것에만 집중하게 된다.
profile
블로그 이사했습니다 🧳

0개의 댓글