본문 바로가기
개발 이야기/React

React로 ProgressBar 구현하기

by 농개 2019. 10. 4.

요즘 몇몇 웹사이트 들어가보면 브라우저 상단에 ProgressBar가 동작하는 것을 확인 할 수 있습니다.

유투브, 구글애드센스 등 브라우저에서 요청을 하면 ProgressBar를 표시해서 사용자에게 나름(?) 사용성을 향상 시켜줍니다.

 

결과화면입니다. 클릭해야 잘 보이네요...ㅜㅜ

 

해당 글에서는 React 프로젝트에서 상단 ProgressBar를 구현하는 방법을 정리합니다.

아래의 주요 라이브러리를 사용했습니다.

  • CRA(Create React App)
  • Axios(비동기 요청 라이브러리)
  • Material-UI(React UI 라이브러리)

 

01. 관련 라이브러리 설치

1
2
npm install --save axios
npm install --save @material-ui/core
cs

material-uiprogressbar 컴포넌트를 사용할 예정입니다. (https://material-ui.com/components/progress/#progress)

 

 

02. axios.create으로 httpClient 커스터마이징

axios의 인터셉터 기능을 이용해야 합니다. 

요청/응답 시, 특정 코드를 실행하여합니다.

 

먼저 아래와 같이 껍데기를 만들어 봅시다.

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
import axios from 'axios';
 
const baseURL = (() => {
  if (process.env.NODE_ENV === 'development') {
    return 'http://localhost:3001/'
  }  else {
    return '/'
  }
})();
 
const defaultClient = axios.create({
  baseURL,
  headers: {
    'Accept''application/json',
    'Content-Type''application/json; charset=utf-8',
  }
})
 
defaultClient.defaults.timeout = 3000;
 
defaultClient.interceptors.request.use(function (config) {
  // 요청 인터셉터
  return config
}, function (error) {
  return Promise.reject(error);
});
 
defaultClient.interceptors.response.use(function (response) {
  // 응답 인터셉터
  return response;
}, function (error) {
  return Promise.reject(error);
});
 
export default defaultClient;
cs

axios.create로 axios wrapper를 만들고 baseURL, headers를 설정해줬습니다.

그리고 request와 response에 대한 interceptors 설정하였습니다.

이제 defaultClient로 비동기 요청을 하게 되면 인터셉터가 동작하게 됩니다.

 

 

위에서 선언한 defaultClient 모듈로 아래와 같이 Http 비동기 요청 코드를 작성 할 수 있습니다.

1
2
3
4
5
6
import defaultClient from "../../lib/defaultClient";
 
function getList(){
    const { data } = defaultClient.get('/api/contents', {});
    // 비지니스로직
}
cs

 

03. setProgress, timer 구현

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 axios from 'axios';
 
const baseURL = (() => {
  ...(중략)
})();
 
let progress = 0;        // 0 ~ 100, 요청 진행률
let timerId = null;        // timer id
 
function setProgress(value) {
  progress = value;
}
 
function timer() {
  if (progress < 98) {
    const diff = 100 - progress;
    const inc = diff / (10 + progress * (1 + progress / 100)); // 증가값
    setProgress(progress + inc);
  }
  timerId = setTimeout(timer, 50); // 50 ms 단위로 timer 재귀호출
}
 
...(중략)
 
defaultClient.interceptors.request.use(function (config) {
  setProgress(0);
  timer();                // HTTP 요청시 timer 실행
  return config
}, function (error) {
  return Promise.reject(error);
});
 
defaultClient.interceptors.response.use(function (response) {
  if (timerId) {
    clearTimeout(timerId);    // HTTP 응답시 timer 해제
    timerId = null;
  }
  setProgress(100);            // 진행도: 100 = 요청/응답 완료
  return response;
}, function (error) {
  return Promise.reject(error);
});
 
export default defaultClient;
cs

위와 같이 작성해줍니다.

요청 시작 시 progress = 0, timer 시작

응답 완료 시 progress = 100 timer 중지

 

 

04. Progress 컴포넌트 작성

./component/MyProgress.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
import React from 'react'
import LinearProgress from '@material-ui/core/LinearProgress';
import { makeStyles, createStyles } from '@material-ui/styles';
import { Fade, createMuiTheme } from '@material-ui/core';
 
const useStyles = makeStyles(() =>
  createStyles({
    bar: {
      position: 'fixed',
      top: 0,
      left:0,
      width: '100%',
      zIndex: 9999
    },
  }),
);
 
const Progress = (props) => {
  const classes = useStyles({});
  return (
    <Fade in={props.active}>
      <div className={classes.bar}>
          <LinearProgress 
            color="primary" 
            variant="determinate" 
            value={props.progress}
          />
      </div>
    </Fade>
  )
}
 
export default Progress
 
cs

props는 아래 두개를 인자로 받을 것입니다.

  • active : boolean, 화면표시 여부
  • progress : number, 진행도(0 ~ 100)

그리고 material-ui에서 제공하는 css-in-js 모듈, createStyles로 css를 작성했습니다.

 

 

05. Progress 컨테이너 작성

./container/MyProgressContainer.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
53
import React, { Component } from 'react'
import MyProgress from '../components/MyProgress';
 
class ProgressContainer extends Component {
  constructor(props: Props){
    super(props);
    this.state ={
      progress: 0,
      active: false
    }
  }
 
  componentDidMount(){
    window.progressbar = this // 중요, window 전역에 ProgressContainer 할당
  }
 
  onChange = (value) => {
    if(value === 100){
      // 응답완료 시, initProgress 콜백 호출
      this.setState({
        progress: value,
        active:true
      }, this.initProgress)
    } else{
      // progress 가 변할때마다 state 갱신
      this.setState({
        progress: value,
        active:true
      })
    }
  }
 
  initProgress = () => {
    setTimeout(() => {
      this.setState({
        active:false
        progress: 0
      })
    }, 1000)
  }
 
  render() {
    return (
      <MyProgress 
        active={this.state.active} 
        progress={this.state.progress}
      />
    )
  }
}
 
export default ProgressContainer
 
cs

 

06. setProgress 수정

1
2
3
4
5
function setProgress(value: number) {
  progress = value;
  window.progressbar.onChange(progress); // ProgressContainer의 함수 호출 
}
 
cs

앞서 작성한 defaultClient 모듈에 한줄 추가해줍니다.

 

 

그러고 나서 defaultClient로 HTTP 요청을 보내보세요.

상단에 Progressbar가 표시되고 

완료되면 사라지는걸 확인 하실수 있습니다.