옛글/Frontend

[React+Rails] 주기적으로 업데이트 되는 위젯을 만들어보자

ShakeJ 2019. 3. 26. 14:34

지난 번 Rails React 개발 환경을 마친 뒤, Jquery, Rails View, Html로 구성되어 있던 기존 프로젝트의 부분 부분을 변경해보고자 합니다. 


가장 먼저 만만하게 잡힌 곳은, 바로 요 부분입니다. 



기존에는 Rails view에서 Rails Model에 Query를 요청해서 표시를 했습니다. 

React로 변경하면, 얻는 이점은 

- Rails는 철저하게 백엔드의 역활만 수행하도록 분리한다. (유지보수 측면)

- 일정 시간이 지난 뒤 시세를 변경할 때 최소한의 리소스로 빠르고 간견할 코드로 업데이트가 가능하다.(Virtual Dom - 자세한 내용은 여기)

입니다. 


일단 React에 대한 별다른 기본 지식이 없기 때문에 기능 구현을 하며 이해하고 정리해봅니다. 



기존 코드의 경우 모델에 직접 요청을 하는 Rails 코드가 view안에 포함되어 있고, Rails 코드로 값을 가져오고 있습니다. (크흡 코드가 더럽습니다)

일단 기존 HTML 단에서 react_comopenet를 요청할 수 있도록 변경합니다.


1
2
3
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-6">
 <%= react_component("CoinPrice", { coin_name: "BCH" }) %>
</div>
cs


React component 중 CoinPrice라는 컴포넌트를 호출하도록 했습니다. 


app/javascript/ 폴더에 CoinPrice.js 파일을 생성합니다.


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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import React from "react"
import PropTypes from "prop-types"
 
const API = '';
 
class CoinPrice extends React.Component {
  constructor(props) {
    super(props);
 
    this.state = {
      price_krw: [],
      percent_change_1h: []
    };
  }
 
  componentDidMount() {
    try {
      setInterval(async () => {
        const res = await fetch(API + "?coin_name=" + this.props.coin_name);
        const blocks = await res.json();
        this.setState({
          price_krw: parseInt(blocks.coin.price_krw, 10),
          percent_change_1h: blocks.coin.percent_change_1h
        })
      }, 30000 * 6);
    } catch(e) {
      console.log(e);
    }
 
    fetch(API + "?coin_name=" + this.props.coin_name)
      .then(response => response.json())
      .then(data => this.setState({
        price_krw: parseInt(data.coin.price_krw, 10),
        percent_change_1h: data.coin.percent_change_1h
    }));
  }
 
  render() {
    let html = "";
    html = <div className="display_inline">
              <div className="float_left"><b>{this.props.coin_name}</b></div>
              <div className="top_header_right_border"></div>
              <div className="float_right" style={{color: this.state.percent_change_1h > 0 ? "red" : "blue"}}>{this.state.price_krw}원 ({this.state.percent_change_1h}%)</div>
            </div>
 
 
    return html;
 
  }
}
export default CoinPrice
 
cs


state에 변수를 저장하고, 파라미터 (props)로 받은 코인 이름에 따라 API를 요청하고 받은 json을 render()합니다. 


fetch를 받는 부분이 2군데에 중복되어 있어 리팩토링을 합니다. 



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
34
35
36
37
38
39
40
41
42
43
44
import React from "react"
import PropTypes from "prop-types"
 
const API = '';
 
class CoinPrice extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      price_krw: [],
      percent_change_1h: []
    };
  }
 
  componentDidMount() {
    this.intervalId = setInterval(() => this.loadData(), 3000 * 10);
    this.loadData();
  }
 
  componentWillUnmount() {
    clearInterval(this.intervalId);
  }
 
  loadData() {
    fetch(API + "?coin_name=" + this.props.coin_name)
      .then(response => response.json())
      .then(data => this.setState({
        price_krw: parseInt(data.coin.price_krw, 10),
        percent_change_1h: data.coin.percent_change_1h
      }));
  }
 
  render() {
    let html = "";
    html = <div className="display_inline">
              <div className="float_left"><b>{this.props.coin_name}</b></div>
              <div className="top_header_right_border"></div>
              <div className="float_right" style={{color: this.state.percent_change_1h > 0 ? "red" : "blue"}}>{this.state.price_krw}원 ({this.state.percent_change_1h}%)</div>
            </div>
    return html;
  }
}
export default CoinPrice
 
cs


훨씬 깔끔해졌습니다. 

데이터를 요청할때에는 loadData를 사용하고, coponentWillUnmount() 시 setInterval을 그만 호출하도록 초기화했습니다. 


componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().

You should not call setState() in componentWillUnmount() because the component will never be re-rendered. Once a component instance is unmounted, it will never be mounted again.


테스트를 해보니, 정상적으로 주기적으로 데이터를 요청하고 받아와서 해당 Element만 새롭게 Render 됩니다. 


render 내에 기존 HTML에서 사용하는 class 대신 className을 사용합니다. 


표기되는 가격 부분에 원화 표기를 위한 컴마를 붙여야 합니다. 


{this.state.price_krw} 로 표기되는 곳에 javascript의 정규식을 넣어봅니다.


{this.state.price_krw.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}원



{} 안에서는 javascript가 정상적으로 동작을 하는군요. (오.. 매우 편리합니다)