Tiketo 프론트엔드 개발 회고록

Tiketo 프론트엔드 개발 회고록
Photo by Xie lipton / Unsplash
GitHub - syjn99/tiketo-web
Contribute to syjn99/tiketo-web development by creating an account on GitHub.

집약적인 개발

급하게 프로젝트를 준비하느라, 레포 만드는 것부터 제출까지 3일이라는 시간만 있었다. 이틀 밤을 꼬박 샜고, 두번째 날에는 React Native 프로젝트를 통째로 React 웹으로 가느라 시간이 더 오래 걸렸다.

그래도 오랜만에 하나의 프로젝트에 온 신경을 곤두세우며 집중을 하니 기분이 너무 좋았다. 프로젝트 경험이 많지 않은 나에게 이번 개발 경험은 꽤나 ‘우당탕탕’이었지만, 그 과정에서 배우는 것들이 많았다. 지난 8월 구글 캘린더 클론 프로젝트를 하며 실제 코딩을 하는 것보다 구조를 디자인하는 과정에 시간을 더 많이 써야 한다는 점을 배웠는데, 이번에 App Architecture와 Flow Chart 등을 그려보면서 설계에 시간을 많이 썼다는 것도 고무적이었다.

React Native와 Expo에서 헤메다

프로토타입을 앱으로 만들기로 계획했기 때문에, React Native를 쓰고자 했다. 제출 마감까지 시간도 매우 짧았기 때문에, lean하게 테스팅을 해볼 수 있는 Expo라는 툴을 이용했다. Expo가 주는 쾌적한 개발 환경 덕분에 기분 좋게 코딩을 할 수 있었다. 그러나…

Caver-js 모듈 에러문제 문의
안녕하세요 klaytn_address로 caver-js를 이용하여 balance구하려고 하는중에 caver-js 모듈을 깔면 아래와같이 에러메세지가 쭉 뜨더라구요… 그래서 에러에 해당하는 모듈들(install ‘stream-http’, install ‘https-browserify’, install ‘url’, install ‘crypto-browserify’, install ‘stream-browserify’, )을 설치했더니 그래도 계속 같은 에러가 떠서 문의를 남깁니다. ㅠㅠ ERROR in ./node_modules/caver-js/packages/caver-core-requestmanager/caver-providers-http/src/index.js 31:13-28 Module not found: Error: Can’t resolve ‘http’ in ’/Users/imhyeonjeong/Desktop/blockchain/node_modules/caver-js/p…

클레이튼 노드와 HTTP를 통해 상호작용하기 위한 라이브러리 caver-js를 사용하며 문제가 발생했다. “Error: Unable to resolve module XXX”이라는 Webpack 관련 이슈였다. React Native에서 사용하는 모듈과 caver-js에서 사용하는 모듈이 서로 맞지 않아 생긴 에러였다. 자주 쓰이는 모듈들을 ‘RN화’해서 사용할 수 있는 node-libs-react-native라는 패키지도 사용해 봤지만, Expo에서 라이브러리를 다루는 방식이 매우 제한적이라… 결국 문제를 해결하지 못하고 React를 이용해 모바일 웹페이지를 구현하는 것으로 급하게 계획을 변경했다. 아래 repo는 Expo로 고군분투했던 흔적이다.

https://github.com/syjn99/ticketo-app

React Native와 Expo로 개발할 때는 사용하고자 하는 라이브러리와의 호환성을 꼭꼭 체크하자…! (검색해보니 React Native를 사용하는 대부분의 개발자들이 이런 라이브러리 호환성 문제를 겪고 있었다.)

redux-persist 사용

Persist state with Redux Persist using Redux Toolkit in React - LogRocket Blog
With Redux Persist, developers can save the Redux store in persistent storage so even after refreshing the browser, the site state will be preserved.

이번 프로젝트에서, 유저에게 보여줘야 할 상태는 다음과 같았다.

  • 유저의 현재 잔고
  • 유저가 가지고 있는 티켓 종류
  • 현재 구매 가능한 티켓들

모든 정보가 블록체인 상에 올라가 있기 때문에, 매번 Caver를 사용해서 불러오기엔 시간 소요가 엄청 났다. 그래서 초기에 로그인하는 과정에서 최대한 많은 정보들을 한 번에 불러오고, 이를 저장하기 위한 상태 관리 툴로 Redux를 사용했고, 유저가 새로고침을 하거나 앱을 껐다가 다시 켜도 지갑이 연결된 상태를 유지하기 위해 redux-persist를 덧붙였다.

redux-persist를 사용할 때는 위 링크를 참고했다.

공부를 다시 한 개념들

async, await, Promise

클레이튼 네트워크를 서버처럼 사용했기 때문에, 노드와 통신해서 데이터를 가져오고, 트랜잭션을 보내는 등의 비동기 프로그래밍이 필수적이었다. 이때 정말 많이 사용했던 키워드가 바로 asyncawait이었다.

asyncawait을 이해하기 위해선, Promise라는 객체에 대해 이해를 더 할 필요가 있었다. Promise는 자바스크립트 객체다. 현재에는 당장 얻을 수는 없지만 가까운 미래에는 얻을 수 있는 어떤 데이터에 접근하기 위한 방법을 제공한다. 어떤 함수의 리턴값으로 new Promise를 리턴해주는 경우도 있지만, fetch() 등의 함수가 리턴하는 Promise를 then(), catch() 메소드를 이용해 특정 로직을 수행하게 하는 것이 일반적이다. then() 메소드에는 결과값을 가지고 수행할 로직을 담은 콜백 함수를 인자로, catch() 메소드에는 예외 처리 로직을 담은 콜백 함수를 인자로 받는다.

Promise만으로 비동기 처리를 하면 발생하는 디버깅, 예외 처리, 들여쓰기 등의 문제 때문에 ES7부터는 async, await 키워드가 추가되었다. await 키워드는 async 함수 안에서만 사용 가능하며, 결과값을 얻을 때까지 기다려주고 다음 라인으로 넘어간다. 일반 동기 코드와 비슷하게 코드를 작성할 수 있어 가독성 측면에서도 훨씬 도움이 많이 되었다. 또한 async 함수는 항상 Promise를 리턴하기 때문에, 함수의 결과값을 처리할 때 일반 함수처럼 처리하는 것이 아닌 then() 등의 메소드를 사용해야 한다.

아래 함수는 맨 처음 유저가 Klip으로 지갑을 연결할 때 사용되는 함수이다. REST API를 총 두 번 불러야 했기 때문에, async 함수 안에 또다른 async 함수를 넣었다.

// AuthPage.js

const ConnectWallet = async () => {
    const paramsLogin = { "bapp": { "name": "Ticketo" }, "callback": { "success": '', "fail": '' }, "type": "auth" }

    await axios.post(API_PREPARE, paramsLogin)
      .then((res) => {
        const { request_key } = res.data;
        window.location.href = (`${KLIP_URL}${request_key}`);

        let timerId = setInterval(() => {
          axios.get(`${API_RESULT}${request_key}`)
            .then(async (res) => {
              if (res.data.result) {
                const address = res.data.result.klaytn_address
                const balancePeb = await paperContract.methods.balanceOf(address).call();
                const balance = caver.utils.convertFromPeb(balancePeb, 'KLAY');
                dispatch(login({ address, balance }));
                clearInterval(timerId);
              }
            })
        }, 1000);
      })
      .catch((error) => console.log(error));
  }

Mount vs. Render

마운트(Mount)와 렌더(Render) 차이

useEffect Hook을 사용할 때 가장 많이 사용되는 방식은 바로 두번째 파라미터에 비어있는 배열을 넣는 것이다. 이는 컴포넌트가 마운트될 때만 실행하고 싶을 때 사용한다.

마운트(Mount)란, React가 처음으로 컴포넌트를 렌더링(Rendering)하고 실제로 초기 DOM을 빌드할 때를 의미한다. 이때 렌더링이란 DOM 생성을 위해 함수가 호출될 때를 의미한다.

흔히 React에서 props나 state 등이 변경되면 컴포넌트가 ‘리렌더링(Rerendering)된다고 한다. 이때 이미 마운트되어 있는 컴포넌트에 바뀐 요소만 변경하는 것이기 때문에, 리렌더링 과정에서 마운트는 거치지 않는다.