⚠️ 해당 프로젝트는 ZUM 인터넷의 기술과제였습니다. ZUM 기술 블로그에도 구현사항들이 어느정도 오픈되어 있습니다. 따라서 깃허브로 제출하는 과제였기도 했고 기한 내에 제출하지 못해서 개인적인 욕심으로 더 진행해보는 프로젝트입니다.
리액트를 배우다보니 '굳이 왜 이렇게 어려운 문법을 배워가면서 사용해야할까?'란 생각이 들었다. 이런 생각은 내가 리액트를 제대로 이해하지 못했거나, 필요성을 느끼지 못해서라고 생각했다. 많은 기업에서 리액트라는 기술 스택을 요구하고 있고, 실제로 잘 사용하고 있는듯하다. 그러면 Vanilla JS로 리액트스럽게 만들면 필요성을 느끼지 않을까? 정확히는 SPA를 만들어봐야겠단 생각이 들었다.
이런 과정에서 웹팩, 프론트엔드 개발환경, 바벨, CSR, SPA, 상태(State), 컴포넌트, 선언형 프로그래밍 등에 대한 생각들을 깊게하게 되고 많은 것들이 추상화되어 있다는 것을 알게되었다. 내가 너무 편하게 개발을 하고 있었던 것을 오히려 어렵게 생각했던 것이었다. 간단한 함수 사용법만 익히면 구현이 가능하던 것들을 Vanilla JS에선 직접 구현해야하다보니 비교도 해보면서 구현하는 재미까지 있었다.
아직까지 구체적으로 떠오르진 않지만 조금씩 퍼즐이 맞춰지는 느낌이다. '이건 이 기능이네, 이건 완전히 감춰져 있었구나.'라는 생각을 하며 리액트를 그리워하고 있다. 이 프로젝트도 개인적으로 욕심이 나서 내가 원하는 기능을 다 구현할 때까지 해보고싶다. 그 다음 리액트를 배우면 더욱 성장을 할 수 있지 않을까.
-
Front-end
- Vanilla JS(ES6)
- HTML5, CSS3
- Axios
-
Back-end
- Node.js
- Express.js
- Cheerio
- Axios
- ✅ 컴포넌트 기반 설계
- 상태(State)를 기반으로 렌더링
- ✅ 전역 상태관리를 위한 Store
- Vuex 또는 Redux 등과 같은 라이브러리를 왜 사용할까?
- 관심사 분리?
- 직접 만들어보고 사용해보기
- Vuex 또는 Redux 등과 같은 라이브러리를 왜 사용할까?
- ✅ 페이지간에 이동이 발생할 때 새로고침이 발생하지 않도록 하기
- 상세 페이지 진입
- (미완성) 서브페이지 진입
- ✅ 상세 페이지는 원본 URL을 크롤링하여 렌더링
- 3개의 크롤링 라이브러리 비교 후 예제와 문법이 익숙한 cheerio로 구현
- ✅ Image lazy loading 구현하기
- 웹 성능을 위해 상세 페이지의 이미지를 lazy loading하기
- Intersection Observer API를 사용하여 구현했다.
- ✅ 제공된 JSON을 토대로 API End-point 만들기
- 처음 서버쪽을 구현해보았다. 동작은 하지만 좋은 코드인지, 좋은 설계인지는 모르겠다 그래도 처음보다 리팩토링을 해서 보기에 나쁘진 않다.
- 🔼 라우팅 구현
- 내가한 게 좋은 코드인지 잘 모르겠다.
- 많은 예시들을 찾지 못했고, 거의 비슷한 방식으로 구현해서 많은 고민이 필요했다.
- 처음 구현하는 것이라서 어느 것이 더 나은 방법, 더 좋은 코드인지에 대해 시간을 쏟았다.
- 원하는 대로 잘 동작하지만 해당 흐름이 맞는건지 더 생각해보자.
- history API의 pushState 메서드를 사용해서 뒤로가기, 앞으로가기를 구현했다.
- 내가한 게 좋은 코드인지 잘 모르겠다.
- 🔼 웹팩으로 개발환경 구축하기
- 거의 3일 내내 웹팩을 학습하는데에 몰입했다. 강력한 툴임은 확실하지만 아직 익숙하지 않다. 개발환경 구축과 번들링 등 여러가지를 시도 중이다.
- 💡 공통 사항
- 좋은 코드인지 항상 고민하고 유지보수 하자.
토스ㅣSLASH 21 - 프론트엔드 웹 서비스에서 우아하게 비동기 처리하기
웹 서비스, 또는 UI 개발에서 가장 다루기 어려운 부분은 무엇일까? 영상에선 10여 년 전에 비해 많은 부분들이 개선되어 개발자가 신경써야 할 것들이 적어졌지만 그럼에도 어려운 영역은 '비동기 프로그래밍'이라고 한다. 시작부터 공감이 되어서 집중해서 보았다. 그리고 프로젝트에 적용해보고자 한다.
const category = ['life', 'food', 'trip', 'culture'];
const contents = new Contents(httpClient);
for (const name of category) {
contents //
.getContents(name)
.then((result) => {
store.setState({ [name]: result });
render();
});
}
contents //
.realTimeBest()
.then((result) => {
store.setState({ rankingContent: result });
render();
});
위의 예시는 리팩토링 전 비동기 코드다. 해당 코드의 문제점은 무엇일까?
- 코드가 중복되고 함수가 하는 역할이 잘 보이지 않는다.
- 위 코드를 보고 무슨 일을 하는지 알 수 있을까..?
- 성공하는 경우와 실패하는 경우가 섞여서 처리된다.
- 심지어 나는 에러처리도 안하고 있었다..😵
- 코드를 작성하는 입장에서 매번 에러 유무를 확인해야 한다.
async function fetchContents() {
const life = await contents.content('life');
const food = await contents.content('food');
const trip = await contents.content('trip');
const culture = await contents.content('culture');
const best = await contents.best();
store.setState({
hubContent: { life, food, trip, culture },
rankingContent: best,
});
return render();
}
이번엔 어떨까? 영상에서 말하듯이 함수가 하는 역할이 명확히 드러나는 코드일까? fetchContents()
라는 함수의 이름으로 역할이 드러나고, 동기적인 코드처럼 작성하여 추측하기가 쉽다. 역시 전 코드보다는 확실히 나은 것 같다. 위의 코드는 성공하는 경우만 다루고 실패하는 경우는 catch 절에서 분리해 처리할 수 있다. 또, 실패하는 경우에 대한 처리를 외부에 위임할 수 있고, 필요시 내부에서도 처리가 가능하다. async, await을 이용하면 비동기 코드를 동기적인 코드와 비슷하게 만들 수 있다.
- 별도로 에러를 처리하는 부분이 없고 모든 에러 처리는 외부에 위임된다.
- 함수가 하는 역할이 명확히 드러난다.
- 중복되는 코드를 줄이고 간결해졌다. 보기가 쉽다.
- 위 함수는 성공하는 부분만 책임지고, 다른 경우는 외부에 더 잘할 수 있는 부분에 위임한다.
- 필요시 내부에서 try catch로 처리할 수도 있다.
- 프로젝트 Fork 하기
오른쪽 상단 Fork 버튼
- 가져온 프로젝트 Clone 하기( Clone or download 버튼 )
git clone 복사한 url
- 의존성 모듈 설치하기
npm install
- 로컬 서버 켠 후 살펴보기
npm start
- 컴포넌트, 상태, API 통신별로 모듈화
- 콘텐츠 통신 API 리팩토링
- 스타일 추가
- 기사 클릭 시 상세 페이지 진입
- 상세 페이지 원본 URL 크롤링
- 상세 페이지 엔드 포인트 추가
- axios, cheerio 추가
- 상세 페이지 이미지 레이지 로딩 구현
- 상세 페이지 스타일 추가
- 상세 페이지 진입 시 history push, 뒤로 가기 클릭 시 홈화면 렌더
- 클라이언트 사이드도 Fetch API에서 Axios로 변경
- Store의 상태(State) 리팩토링
- 서버, 라우터 리팩토링
- 개발 환경 구축(웹팩)