context

WooBuntu·2021년 3월 28일
0

알고 쓰자 리액트

목록 보기
4/11

https://ko.reactjs.org/docs/context.html

context를 사용하면 컴포넌트를 재사용하기 어려워지므로 꼭 필요할 때만 사용할 것

일반적으로 locale, theme, data cache등을 관리하기 위한 용도로 context를 사용한다

React.createContext

const MyContext = React.createContext(defaultValue);

context 객체를 만드는 함수이다.

이 context 객체를 구독하는 컴포넌트를 렌더링할 때, 해당 컴포넌트는 자신을 감싸고 있는 Provider중 가장 가까운 Provider로부터 현재의 context value를 읽어낸다.

defaultValue는 컴포넌트 트리 내에서 대응되는 Provider를 찾지 못한 경우에만 쓰이는 값이다.

Context.Provider

<MyContext.Provider value={/* 어떤 값 */}>

Provider는 value prop을 받아서 해당 context를 구독하고 있는 컴포넌트들에게 전달한다. 이 value prop값이 바뀔 때마다 context를 구독하는 컴포넌트들은 다시 렌더링된다. Provider에서 Consumer(useContext포함)로의 데이터 전파에는 shouldComponentUpdate메서드가 적용되지 않으므로, 상위 컴포넌트가 업데이트를 건너 뛰더라도 구독 컴포넌트는 업데이트된다.

shouldComponentUpdate를 통한 얕은 비교를 수행할 수 없으므로 다음과 같이 객체 리터럴을 value prop으로 전달해버리면 매번 새로운 객체가 생성되므로 Provider가 렌더링 될 때마다 구독 컴포넌트들이 몽땅 재렌더링되는 불상사가 발생한다.

const App = () => {
  return (
    <Provider value={{something: 'something'}}>
      <Toolbar />
    </Provider>
    );
}

위와 같은 문제를 피하기 위해 아래와 같이 value를 부모 컴포넌트의 state값으로 끌어올린다.

const App = () => {
  const [value, setValue] = useState({something: 'something'});
  
  return (
    <Provider value={value}>
      <Toolbar />
    </Provider>
    );
}

Context.displayName

React.createContext로 생성한 context객체는 displayName 문자열 속성을 설정할 수 있다.

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools

위와 같이 displayName 속성을 설정하면 리액트 개발자 도구에서 context를 displayName으로 확인할 수 있다.

useContext

React.createContext가 반환한 context객체를 인자로 받아 해당 context의 현재 값을 반환한다. Provider가 갱신(렌더링)되면 갱신된 value값을 받아 useContext가 호출된 컴포넌트를 재렌더링한다. 상위 컴포넌트에서 React.memo나 shouldComponentUpdate를 사용하고 있더라도 useContext를 호출한 컴포넌트는 재렌더링된다.

그렇기에 해당 컨텍스트를 구독하고 있는 컴포넌트는 해당 컨텍스트와 관련된 값만을 렌더링하도록 구성해주는 것이 좋다.(ex: theme컨텍스트를 구독하고 있는 컴포넌트는 theme과 관련된 정보만 렌더링하도록 구성하는 것이다)

위와 같은 방식으로 컴포넌트를 구성하기 어려울 때 쓸 수 있는 대안이 두 가지 있다.

첫번째는 React.memo를 사용하는 것이다.

function Button() {
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"
  return <ThemedButton theme={theme} />
}

const ThemedButton = React.memo(({ theme }) => {
  // The rest of your rendering logic
  return <ExpensiveTree className={theme} />;
});

이 경우 AppContext에서 theme과 관련 없는 값이 바뀌면 Button은 재렌더링되겠지만, ThemedButton은 theme이 변하지 않았기 때문에 재렌더링되지 않는다.

두번째는 useMemo를 사용하는 것이다.

function Button() {
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"

  return useMemo(() => {
    // The rest of your rendering logic
    return <ExpensiveTree className={theme} />;
  }, [theme])
}

이 경우도 마찬가지로 Button은 재렌더링되겠지만 ExpensiveTree는 theme이 변경되지 않는 한 재렌더링되지 않는다.

context구독자가 context값을 변경할 수 있도록 만들기

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext({
  theme: themes.light,
  toggleTheme: () => {}
});
// 이 곳에서 toggleTheme을 함수로 정의한 것은 이후 형을 맞춰주기 위함인 것으로 보인다.

function App() {
  const [theme, setTheme] = useState(themes.dark);
  const toggleTheme = () => setTheme((prevTheme) => prevTheme === themes.dark ? themes.light : themes.dark);
    
  return (
    <ThemeContext.Provider 
      value={{
        theme,
        toggleTheme
      }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const {theme, toggleTheme} = useContext(ThemeContext);
  return (
    <button 
      style={{ background: theme.background, color: theme.foreground }} 
      onClick={toggleTheme} >
      I am styled by theme context!
    </button>
  );
}

여러 context구독하기

// 기본값이 light인  ThemeContext
const ThemeContext = React.createContext('light');

// 로그인한 유저 정보를 담는 UserContext
const UserContext = React.createContext({
  name: 'Guest',
});

const App = ({signedInUser, theme}) => {
  return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// 여러 context의 값을 받는 컴포넌트
function Content() {
  const theme = useContext(ThemeContext);
  const user = useContext(UserContext);
  return <ProfilePage user={user} theme={theme} />
}

0개의 댓글