[좌충우돌 개발기] 앗! 리액트 + 하이차트로 차트를 그렸는데 차트가 갱신이 안되네!?
안녕하세요, 플랫폼별 QR, 바코드 스캐너 구현기를 썼던 이동훈입니다.
최근 대시보드 화면 구현 중 기본을 놓쳐서 시간을 낭비한 경험이 있었습니다. 이번 글에서는 삽질기를 돌아보고, React + Highchart 조합을 사용하여 대시보드 구성시 주의해야 할 데이터 초기 로딩 방법
과 데이터 변경 시 차트 리렌더링은 어떻게 해야 하는지에 관하여 이야기해보려 합니다.
Highchart
구현에 사용한 기술 중 Highchart에 관해서 위키를 통해 잠시 살펴보면
Highcharts is a software library for charting written in pure JavaScript, first released in 2009.
pure라는 단어와 2009년에 처음 배포가 되었다는 사실이 눈에 들어옵니다.
구현해야 할 사항들
- 로그인 시 첫 화면에 당일 기준 매출 금액, 매출 건수, 반품 금액, 반품 건수를 보여주는 차트를 구현한다.
- 1일, 1주, 1달, 1년의 기간을 설정할 수 있는 버튼이 존재하고 버튼을 눌렀을 때, 데이터가 갱신되고 차트도 갱신된다.
- 고객 데모시연용 화면임
- 데모까지 2일, 구현 후 내부 확인 까지 1일의 기간이 주어짐.
삽질기 소개
최대한 빠르게 React + Highchart 사용 예제를 찾아서 확인한 후, 원하는 모양의 정적 json을 차트 컴포넌트에 바인딩 해 보았습니다.
나: 잘 나오네 ㅎㅎ React + Highchart 짱짱! API만 만들어서 붙이면 되겠다~
API를 만들고 차트 데이터를 state 변수에 바인딩 한 후, state 가 바뀔 때, 차트도 바뀌길 기대하며 확인해 보았습니다. 하지만... 데이터만 갱신이 되고, 차트는 갱신이 되지 않았습니다.
나: (이상하다..., 분명 state 변수는 갱신이 되는데, 왜 차트가 안 바뀌지???)
잠시 삽질후..
나: (빨리 돌아가는 화면이 나와야 하는데...) 김이사님 도와주세요~김: 여기(사내 다른 프로젝트 코드) 보고 하세요.나: 아..잘되네요 ㅎㅎ(일단구현)
구현시 겪었던 문제분석
코드분석
시간에 쫓겨 코드를 잘 살피지 못했었기 때문에 주말을 이용해 코드를 다시 살펴보았습니다.
문제는 copy & paste 했던 코드 와
1 2 3 4 5 6 7 8
... componentDidMount() { this.chart = new Highcharts[this.props.type || 'Chart']( this.chartContainer.current, this.props.options ); } ...
HighCharts React Official 코드를 비교해 보니 쉽게 알 수 있었습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
... componentDidMount() { const props = this.props; const highcharts = props.highcharts || window.Highcharts; // Create chart this.chart = highcharts[props.constructorType || "chart"]( this.container.current, props.options, props.callback ? props.callback : undefined ); } componentDidUpdate() { if (this.props.allowChartUpdate !== false) { this.chart.update( this.props.options, ...(this.props.updateArgs || [true, true]) ); } } ...
갱신이 안되었던 코드에는 update 로직이 없었던 것이었습니다. 하지만 조금 이상합니다. "React는 원래 부모의 state를 갱신하면 자식의 render 도 수행되는 것 아닌가?" 하는 의문이 드는데요. 아래 내용과 연관이 있습니다.
Dom 바인딩
HighChart는 차트 생성 시 container라고 부르는 Dom component 가 필요한데요.
1 2
this.chart = Highcharts.chart('chart', this.state.options); <div id="chart"></div>
위와 같은 코드입니다. 이 부분을 통해 HighChart는 id를 참조하여 차트가 그려질 공간에 바인딩 되는 것을 알 수 있습니다. 여기서 드디어 첫 코드에서는 state 가 바뀌었는데 차트가 다시 그려지지 않은 실마리를 찾을 수 있었습니다. HighChart 가 차트를 그리는 방법은 그려질 위치, 차트에 관한 옵션 를 인자로 받아 pure 자바스크립트로 된 차트 객체를 생성하여 dom에 바인딩 하는 방식으로 차트를 그리고 있었습니다. 그런데, 첫 번째 코드에서는 componentDidMount에서 1회만 차트 객체가 생성되기 때문에 state 가 변경이 되어도 차트가 갱신되지 않는 것이었습니다.
2번째 코드에서 차트의 갱신 원리를 그림과 함께 정리해보면 아래와 같습니다.
- 부모 컴포넌트의 state에 차트 데이터에 해당하는 options가 할당이 되어 있습니다.
- API를 통해 options의 데이터가 갱신이 완료되면, 자식 컴포넌트의 componentDidUpdate가 실행됩니다.
- componentDidUpdate 내부에선 HighChart 가 제공하는 update 함수를 이용하여 차트를 갱신합니다.
주목할 점은 차트를 갱신하는데 React에서 제공하는 state 변수를 사용한 것이 아니라 this.chart.update 즉, pure javascript HighChart에서 제공하는 update 함수를 사용했다는 점입니다. (왜 react인데 react처럼 쓰지를 않는 거니..) (참고: HighChart Official Blog)
차트 갱신시 State 는 사용하지 못하는 건가?
이쯤에서 제가 해결했던 코드를 살펴보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
class App extends Component { constructor(props) { super(props); this.chart = ""; this.state = { options: { ... } } componentDidMount() { this.loadChartData(); }; loadChartData = () => { this.setState({options: {...this.state.options, series: [{ ... }, ]}}, () => this.chart = Highcharts.chart('chart', this.state.options)); }; ... render() { return ( <div className="App"> <div> <button onClick={this.loadChartData}>change</button> </div> {this.state.loading ? (<div>Loading...</div>) : (<div id="chart"></div>)} </div> ); } } export default App;
위 코드를 보면 componentDidMount에서 초기 데이터를 호출했고, event 함수(onClick)에 데이터 갱신 함수(loadChartData)를 바인딩 하여 차트를 갱신했습니다.
1
this.chart = Highcharts.chart('chart', this.state.options)
꺼림칙한 부분은 위 코드인데요, 데이터가 갱신될 때마다, this.chart 변수에 새로운 Chart 객체가 할당되는 것을 볼 수 있습니다. 여기까지 정리해 보고 나니 현재 코드의 개선점을 찾은 것 같습니다.
개선할 점은 차트를 매번 새로 생성하여 할당하는 부분을 HighChart에서 제공하는 update 함수를 사용하여 아래와 같이 수정하는 것입니다.
1 2 3 4 5 6
loadChartData = () => { this.setState({options: {...this.state.options, series: [{ ... }, ]}}, () => this.chart.update(this.state.options)); };
정리
데이터 초기 로딩은 어디서 해야 하나?
- componentDidMount에서 하면 됩니다. (주의할 점: 초기 데이터 로딩전 HighChart 객체를 생성하여 Dom에 바인딩 해주어야 합니다.)
데이터 변경 시 차트 리렌더링은 어떻게 하나?
- 차트 데이터는 state에 바인딩 한다.
- Highart 가 제공하는 update 함수를 사용하여 차트를 갱신
- 부모 + 자식 컴포넌트 방식을 사용했다면, 자식 컴포넌트의 componentDidUpdate 안에서 호출.
- 1개의 컴포넌트에서 사용했다면, event 함수 안에서 먼저 setState를 통해 차트 데이터를 갱신하고, setState의 콜백에서 호출.
생각해보면 React Life Cycle에 대한 동작원리와 HighChart 가 React 환경에서 어떻게 동작하는지 조금만 생각해 보았으면 삽질을 안 했을 수도 있었을 것 같습니다. 기본에 충실해야 하는 것을 잊지 말고자 회고 성격의 글을 작성해보았습니다. 읽어주셔서 감사합니다.