React 16.3 Context API로 다국어 시스템 리팩토링

문제 상황

회사 서비스의 다국어 지원을 위해 locale 상태와 t() 번역 함수를 최상위 컴포넌트에서 관리하고 있었다. 문제는 이를 하위 컴포넌트로 전달하기 위해 5~6단계의 props drilling이 발생했다는 점이다.

<App locale={locale}>
  <Layout locale={locale}>
    <Page locale={locale}>
      <Section locale={locale}>
        <Component locale={locale} />
      </Section>
    </Page>
  </Layout>
</App>

Redux를 도입하기엔 이 기능 하나 때문에 오버엔지니어링이라 판단했고, React 16.3에서 정식 릴리즈된 새로운 Context API를 적용하기로 했다.

구현

// LocaleContext.js
import React from 'react';

const LocaleContext = React.createContext({
  locale: 'ko',
  t: (key) => key,
  changeLocale: () => {}
});

export default LocaleContext;
// App.js
class App extends React.Component {
  state = {
    locale: 'ko'
  };

  changeLocale = (locale) => {
    this.setState({ locale });
  };

  t = (key) => {
    return translations[this.state.locale][key] || key;
  };

  render() {
    return (
      <LocaleContext.Provider value={{
        locale: this.state.locale,
        t: this.t,
        changeLocale: this.changeLocale
      }}>
        {this.props.children}
      </LocaleContext.Provider>
    );
  }
}

사용하는 쪽에서는 Context.Consumer로 감싸면 됐다.

<LocaleContext.Consumer>
  {({ t }) => (
    <h1>{t('welcome_message')}</h1>
  )}
</LocaleContext.Consumer>

결과

중간 컴포넌트들이 locale props를 몰라도 되니 코드가 훨씬 간결해졌다. 다만 Consumer 패턴이 render props 형태라 약간 장황한 느낌은 있다. Hooks가 나오면 이 부분도 개선될 것 같은데, 아직은 RFC 단계라 실무 적용은 어렵다.

당분간은 이 방식으로 운영하면서 상태 관리가 더 복잡해지면 그때 Redux 도입을 재검토할 예정이다.

React 16.3 Context API로 다국어 시스템 리팩토링