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 도입을 재검토할 예정이다.