[React.js] StopWatch 구현하기

2021. 7. 21. 00:03개발/React.js

반응형

어떤 프로그래밍 언어든 직접 해보지 않으면, 내가 배운것이 다가 아니라는 것을 계속 깨닫게 된다.

React 공홈을 통해 배운 것을 하나로 뭉쳐서 뭘 만들 수 있을까 고민해보다가 state, component 등을 한눈에 볼 수 있는 stop watch로 정했다.

 

 

 

무엇을 구현할지는 아래와 같이 정했다.

1. Start, Stop 버튼 클릭시 stopwatch 실행, 중단
2. Records 버튼 클릭시 현재 시간 기록
3. Reset 버튼 클릭시 전체 초기화 (시간, records 기록 모두 초기화)

 

 

1. html 에 넣을 component를 먼저 구현

<html>
  <head>
    <!-- Load React. -->
    <!-- Note: when deploying, replace "development.js" with "production.min.js". -->
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  </head>
  <body>
    <div id="stopwatch"></div>
    <script type="text/babel">

    class StopWatch extends React.Component{
      constructor(props) {
        super(props);
        this.state = {
          
        };
      }

      render() {
        return(
          
        );
      }
    }
    ReactDOM.render(<StopWatch/>, document.getElementById('stopwatch'));
    </script>
  </body>
</html>

 

 

2. render 부분에 어떤 것을 넣을지 고민해서 시간을 표시하는 Indicator 부분과 버튼부분, records 부분으로 나누었다.

(그리고, 상태가 변화함에 따라 실시간으로 update할 부분은 모두 state로 꺼내두었다.)

this.state = {
          curTime: 0,
          curTimeStr: '00:00:00.000',
          isStarted: false,
          recordsId: 0,
          records: []
        };
        
 
 
//


render() {
        return(
          <div>
            <div>{this.state.curTimeStr}</div>
            <button>{this.state.isStarted?"STOP":"START"}</button>
            <button>RECORDS</button>
            <button>RESET</button>
            <div>RECORDS HISTORY</div>
           </div>
        );
      }

 

 

3. 각 버튼별 event를 바인딩 시켜주고, 내부 기능들을 구현해 넣었다.

constructor(props) {
  		// (중략)
        this.toggleSwitch = this.toggleSwitch.bind(this);
        this.recordsTime = this.recordsTime.bind(this);
        this.resetTime = this.resetTime.bind(this);
      }
      
// (중략)

toggleSwitch() {
        if(!this.state.isStarted) {
          this.setState({isStarted: true}, () => {
            this.startTick();
          });
        }
        else {
          this.setState({isStarted: false}, () => {
            this.endTick();
          });
        }
      }

      recordsTime() {
        let time = new Date(this.state.curTime).toISOString().substr(11, 8) + "." + this.state.curTime%1000;
        this.setState({
          records: this.state.records.concat({id:this.state.recordsId, time: time})
        }, ()=>{
          this.setState({recordsId: this.state.recordsId+1});
        });
      }

      resetTime() {
        this.setState({records: [], curTime: 0, curTimeStr: '00:00:00.000'})
      }
      
      
// (중략)

render() {
        return(
          <div>
            <div>{this.state.curTimeStr}</div>
            <button onClick={this.toggleSwitch}>{this.state.isStarted?"STOP":"START"}</button>
            <button onClick={this.recordsTime}>RECORDS</button>
            <button onClick={this.resetTime}>RESET</button>
            <div>RECORDS HISTORY</div>
            <div>{this.state.records.map((element)=><div key={element.id}>{element.time}</div>)}</div>
          </div>
        );
      }

 

 

새로배운 것들!

시행착오를 겪은 것들을 몇가지 정리해보면 아래와 같다.

1. this.state에서 list 값을 가질때 map을 이용하되, key값이 필요하다. (key값을 안넣으면 warning이 발생한다.)

참고 자료

https://velog.io/@ranisol/React-Array%EB%A5%BC-%ED%8F%AC%ED%95%A8%ED%95%9C-JSX-%EA%B7%B8%EB%A6%AC%EA%B3%A0-key%EC%9D%98-%EA%B0%9C%EB%85%90

 

https://ko.reactjs.org/docs/lists-and-keys.html

 

2. this.setState 를 할때 참조하는 state가 있다면, callback arrow function으로 순차적으로 실행되게 해야한다.

예를 들어, curTime += 1 을 시키고 나서 Time format으로 출력을 해야하는데 동시에 바꾸게 되면 먼저 바뀐녀석이 먼저 화면에 출력되게 된다.

// 오동작
this.setState({
            curTime: this.state.curTime + 1,
            curTimeStr: new Date(this.state.curTime).toISOString().substr(11, 8) + "." + this.state.curTime%1000 
          })

// 기대 동작
this.setState({
            curTime: this.state.curTime + 1,
          }, () => {
            this.setState({
              curTimeStr: new Date(this.state.curTime).toISOString().substr(11, 8) + "." + this.state.curTime%1000
            })
          })

 

 

오동작의 경우 아래와 같이 Records 한 시각과 현재 시간과 차이가 발생한다.

 

 

 

전체코드

<html>
  <head>
    <!-- Load React. -->
    <!-- Note: when deploying, replace "development.js" with "production.min.js". -->
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  </head>
  <body>
    <div id="stopwatch"></div>
    <script type="text/babel">

    class StopWatch extends React.Component{
      constructor(props) {
        super(props);
        this.state = {
          curTime: 0,
          curTimeStr: '00:00:00.000',
          isStarted: false,
          recordsId: 0,
          records: []
        };

        this.toggleSwitch = this.toggleSwitch.bind(this);
        this.recordsTime = this.recordsTime.bind(this);
        this.resetTime = this.resetTime.bind(this);
      }

      toggleSwitch() {
        if(!this.state.isStarted) {
          this.setState({isStarted: true}, () => {
            this.startTick();
          });
        }
        else {
          this.setState({isStarted: false}, () => {
            this.endTick();
          });
        }
      }

      recordsTime() {
        let time = new Date(this.state.curTime).toISOString().substr(11, 8) + "." + this.state.curTime%1000;
        this.setState({
          records: this.state.records.concat({id:this.state.recordsId, time: time})
        }, ()=>{
          this.setState({recordsId: this.state.recordsId+1});
        });
      }

      resetTime() {
        this.setState({records: [], curTime: 0, curTimeStr: '00:00:00.000'})
      }

      //Event
      startTick() {
        this.tick = setInterval(() => {
          this.setState({
            curTime: this.state.curTime + 1,
          }, () => {
            this.setState({
              curTimeStr: new Date(this.state.curTime).toISOString().substr(11, 8) + "." + this.state.curTime%1000
            })
          })
        }, 1)
      }

      endTick() {
        clearInterval(this.tick)
      }

      render() {
        return(
          <div>
            <div>{this.state.curTimeStr}</div>
            <button onClick={this.toggleSwitch}>{this.state.isStarted?"STOP":"START"}</button>
            <button onClick={this.recordsTime}>RECORDS</button>
            <button onClick={this.resetTime}>RESET</button>
            <div>RECORDS HISTORY</div>
            <div>{this.state.records.map((element)=><div key={element.id}>{element.time}</div>)}</div>
          </div>
        );
      }
    }
    ReactDOM.render(<StopWatch/>, document.getElementById('stopwatch'));
    </script>
  </body>
</html>

 

 

결과

 

 

 

 

 

 

 

 

 

반응형

'개발 > React.js' 카테고리의 다른 글

[React.js] 8. List 와 Key  (0) 2021.08.02
[React.js] 7. 조건부 렌더링  (0) 2021.07.22
[React.js] 6. Event  (0) 2021.07.20
[React.js] 5. State and Lifecycle  (0) 2021.07.20
[React.js] 4. Component와 Props  (0) 2021.07.19