본문 바로가기

Study/React

useContext

props chain & context 

component tree

 

리액트는 화면에 렌더링 할 여러 컴포넌트로 앱을 구성한다. 이 구조를 component tree라고 부르며, 이 때문에 props chaining이 발생한다.

 

  1. 예를 들어 F가 로그인 컴포넌트 이고, 컴포넌트 A와 C에서 로그인과 관련된 data를 필요로 한다고 해보자.
  2. A, C와 F가 공통적으로 공유하고 있는 부모는 App 컴포넌트 뿐이다.
  3. 따라서 F의 로그인 state를 App까지 끌어 올린 후에, 다시 App에서 그 데이터를 A, C의 props으로 전달해 주어야 할 것이다.
  4. 어플리케이션의 규모가 커지면, props chaining은 아래 예제처럼 복잡하고 번거로워 질 수 밖에 없다. 

 

// login form
const onSubmitHandler = (e) => {
    idElement.current.value = '';
    pwElement.current.value = '';
    props.onLogin(idState.value, pwState.value);
};

// login 
const Login = (props) => {
  return (
    <aside>
      <LoginForm onLogin={props.onLogin} />
    </aside>
  );
};

// login page
const Render = (props) => {
  return (
    <section}>
      <div>
        <Logo />
        <Login onLogin={props.onLoginHandler} />
      </div>
    </section>
  );
}

// App
function App() {
  const [logInState, setLogInState] = useState(false);

  const onLoginHandler = (id, pw) => {
    localStorage.setItem('isLoggedIn', '1');
    setLogInState(true);
  };
  // ...

  return (
    <div>
      {logInState && <Main onLogout={onLogoutHandler} />}
      {!logInState && <Render onLogin={onLoginHandler} />}
    </div>
  );
}

 

이러한 문제를 해결하기 위해, context라는 개념을 사용한다. 리액트는 모든 component가 사용할 수 있는 state storage를 제공하는데(component-wide state), 이 저장소를 통해 길고 복잡한 props chain을 거치지 않아도 컴포넌트끼리 state를 공유할 수 있다. 

+) 단, state가 자주(high frequecy, 1초에도 여러번) 바뀌는 경우에는 최적화를 지원하지 않는다. 

How to use context?

// context
const AuthContext = React.createContext({
    isLoggedIn:false,
    onLogin: ()=>{}, 
    onLogout: ()=>{}, 
    
}); 
export default AuthContext;

 

  1. src/ context 폴더를 만들고, 폴더 안에서 js 파일을 만든다. ex) src/ context/ auth.js 
  2. createContext(default) | default context를 전달받아서 context 객체를 반환한다.
    1. default context | 보통 객체 데이터를 전달하며, 이것이 component-wide state가 된다.
    2. context | 컴포넌트를 포함하는 객체이다. => 변수명을 PascalCase로 만든는 것이 convention이다. 
  3. 이 컨텍스트를 사용할 컴포넌트에게 context를 제공하면(provider), 그 컴포넌트와 자식 컴포넌트 들은 이 context를 소비할 수 있다(consumer). App 컴포넌트에 컨텍스트를 제공하면 모든 컴포넌트에서 해당 컨텍스트를 이용할 수 있다.  

provider

// App.js 
import AuthContext from '../../context/auth';

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

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

  const logoutHandler = () => {
    localStorage.setItem('isLoggedIn', '0');
    setIsLoggedIn(false);
  };

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

 

  1. Auth 컨텍스트는 객체 데이터이다. => AuthContext.Provider
  2. value | context가 전달하는 값
    • state가 변경될 때 마다 jsx 코드가 재평가되므로, 이를 이용하면 컨텍스트가 전달하는 값을 동적으로 설정할 수 있다.
  3. provider 컴포넌트로 감싼 모든 자식 컴포넌트와, 자식의 자식 컴포넌트... 에서, 제공한 context에 접근할 수 있다.
  4. 더이상 props으로 isLoggedIn, onLogin, onLogout 값을 전달하지 않아도 된다.  

consumer

Context.Consumer

import AuthContext from '../../context/auth';

const Navigation = (props) => {
  return (
  <AuthContext.Consumer>
   {(context) => {
      if (context.isLoggedIn) {
        return (
          <nav>
          <!-- ... -->
          </nav>
        );
      }
        return;
    }}
  </AuthContext.Consumer>
  );
};

export default Navigation;

 

  1. consumer는 자식 요소로 콜백함수를 포함해야 한다. 
  2. 콜백 함수는 우리가 설정한 context data를 인자로 받는다. 
  3. 이 때 context. 문법을 이용해서, 기본값으로 hard coding한 data에 접근하려면 provider가 없어야 한다.
  4. 그러나 일반적으로 데이터는 기본값으로 고정되어 있지 않고 변환되며(isLoggedIn), 이런 데이터에 접근할 때에는 provider와 그의 value 속성이 있어야 한다. => <AuthContext.Provider value={ {isLoggedIn:isLoggedIn} }>

useContext 

import React, { useContext } from 'react';
import AuthContext from '../../context/auth';

const Navigation = (props) => {
  const context = useContext(AuthContext);
  return (
    <>
      {context.isLoggedIn && (
        <nav className={classes.nav}>
          <!-- ... -->
        </nav>
      )}
    </>
  );
};

 

  1. useContext에 context 객체를 전달하면, 우리가 전달한 context value가 변수에 할당된다. 
  2. consemer를 이용하는 것보다 간단하기 때문에 useContext 훅을 주로 사용한다. 

refactor

// auth.js
import React, { useState, useEffect } from 'react';

const AuthContext = React.createContext({
  isLoggedIn: false,
  onLogin: (id, pw) => {},
  onLogout: () => {},
});

// 컴포넌트를 반환하는 함수 => hooks 사용 가능
export const AuthContextProvider = (props) => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

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

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

  const logoutHandler = () => {
    localStorage.setItem('isLoggedIn', '0');
    setIsLoggedIn(false);
  };

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

export default AuthContext;

// App.js
function App() {
  const context = useContext(AuthContext);
  return (
    <>
      <MainHeader />
      <main>
        {!context.isLoggedIn && <Login />}
        {context.isLoggedIn && <Home />}
      </main>
    </>
  );
}

<!-- index.js -->
ReactDOM.render(
  <AuthContextProvider>
    <App />
  </AuthContextProvider>,
  document.getElementById('root')
);

'Study > React' 카테고리의 다른 글

etc | Image, Icon, Input  (0) 2022.06.21
Rules of Hooks  (0) 2022.06.20
useReducer  (0) 2022.06.19
useEffect  (0) 2022.06.19
useRef  (0) 2022.06.18