토스 SLASH 21 프론트엔드 관련 발표 내용 정리
토스 | SLASH 21
예비 프론트 엔드 개발자로서 보면 좋을 것 같은 발표들을 정리
1. Micro-frontend React, 점진적으로 도입하기
열어보기
- 이 발표에서 소개하는 것
1 |
|
(1) Django MVC 프로젝트에 점진적으로 React를 도입한 방법
토스의 서버사이드에서 HTML 템플릿을 렌더링하는 모놀리식 Django MVC 프로젝트에서 모던 리액트 코드로 어떻게 이전했는지?
- 시작은 create-react-app을 사용해 빠른 프로젝트를 빌드
- 복잡한 webpack, Babel, ESLint 등의 설정을 건너뛰게 해 줄 것이라 믿음
- 완전한 SPA가 아니고 Django와 함께 사용할 때엔 설정의 충돌이 많이 있었음 → 처음부터 설정을 해보자
- webpack-bundle-tracker(wbt), django-webpack-loader(dwl)를 사용
- wbt는 빌드 결과물의 chunk 정보를 JSON 파일로 추출해주고 dwl은 이 JSON파일을 토대로 스크립트 태그나 링크 태그를 생성해줌
- 많은 부분이 리액트로 바뀌고 나니 패키지 관리가 어려워지고 빌드 시간이 길어짐
- 그래서 Micro-frontend 아키텍처를 사용하기로 함
- 기존의 거대한 소스 코드를 독립적인 패키지(인프라, 라이브러리, 서비스 패키지)로 각각 분리함
- 이를 위해 Yarn 2와 Workspace Plugin을 사용 중
- 이로서 소스 코드부터 빌드 설정까지 완벽하게 격리되고, 의존성 지옥을 탈출할 수 있으며, 빌드 속도도 최적화됨
- 빌드 시간을 줄이는 가장 좋은 방법은 빌드를 하지 않는 것
- 소드코드가 바뀐 패키지만 빌드하고 나머지는 기존 빌드 결과물을 재사용
- Git을 통해 패키지의 변화를 캐치(Git의 해시를 사용)하고 변화가 감지된 패키지만 빌드
2. 프론트엔드 웹 서비스에서 우아하게 비동기 처리하기
열어보기
웹 서비스에서 가장 다루기 어려운 부분은 무엇인가?
- jQuery의 명령형 프로그래밍에서 React/Vue와 같은 프레임워크의 사용으로 선언형 프로그래밍으로 전환
- 비동기 프로그래밍은 끊기지 않는 60 프레임의 좋은 사용자 경험을 위해서는 필수
- 예시 코드 1
1 | function getBazFromX(x) { |
문제점
→ 하는 일은 단순하지만 코드가 너무 복잡함
→ 각 프로퍼티에 접근하는 핵심 기능이 코드로 잘 드러나지 않음해결책
→ OptionalChaining 문법을 활용
1 | function getBazFromX(x) { |
- 예시 코드 2
1 |
|
문제점
→ 코드가 너무 복잡함
→ 성공 케이스와 실패 케이스가 섞여서 처리됨
→ 매번 에러 유무를 확인해야 함해결책
→ async-await 문법을 활용하기
→ 비동기처리를 이용해 성공 케이스만을 다루고 실패 케이스는 catch문을 사용해서 처리함으로써 실패 케이스를 외부에 위임할 수 있다
1 | async function fetchAccounts() { |
예시를 통해 알 수 있는 좋은 코드와 나쁜 코드의 특징
- 좋은 코드 : 성공, 실패 케이스를 분리해 처리 가능, 비즈니스 로직을 한눈에 파악할 수 있다
- 나쁜 코드 : 성공, 실패 케이스가 서로 섞임, 비즈니스 로직의 파악이 어려움
비동기를 처리하는 부분을 정의하기
1 | // SWR이나 React Query를 사용하여 비동기를 정의 |
→ 이는 나쁜 코드
비동기 코드가 여러개가 섞이게 되면 비동기 지옥이 됨
2개의 비동기 리소스를 가져올 때 상태가 각각 ‘로딩, 에러, 완료’로 나뉜다면 이 때 상태는 3의 제곱으로 9가지 상태를 가질 수 있음
리액트에서는 더더욱 이 비동기 처리가 어려운데 React팀에서는 React Suspense for Data Fetching도구를 제공함(아직은 실험 버전에서만 사용 할 수 있음)
어떻게 에러 상태와 로딩 상태를 분리하는가?
1 |
|
- Recoil에서는 Async Selector를, SWR, React Query에서는
{ suspense: true}
를 정의해주면 된다
1 | function getUserName(id) { |
- Recoil의 비동기 셀렉터
1 | export const templateSetSelector = selectorFamily({ |
React hooks와 suspense의 유사도
Hooks에서는 useState, useMemo, useCallback, useEffect와 같은 선언적 코드를 통해 웹 서비스의 코드 복잡도를 낮춰줌
실제 상태 관리, 메모이제이션과 같은 작업은 React 프레임워크가 대신 수행함
suspense를 사용할 때에도 컴포넌트에서 비동기적인 리소스를 선언하고 그 값을 읽어온다고 선언하면 컴포넌트를 감싸는 부모 컴포넌트가 대신 수행함
try-catch문을 통해 실패할 수 있는 함수는 throw를 통해 부모 함수로 던지고 이 에러 처리를 부모 함수가 수행함
이런 책임 분리 방식을 대수적 효과라고 함
하지 못한 이야기들…
→ React Concurrent Mode, useTransition, useDeferredValue 등
→ React에서 부분적으로 렌더 트리를 완성함으로써 더 나은 사용자 경험 향상 가능
참고자료1 : 데이터를 가져오기 위한 Suspense (실험 단계)
참고자료2 : Algebraic Effects for the Rest of Us
3. JavaScript Bundle Diet
열어보기
- 사용자는 느린 로딩을 참지 못한다 (5초 초과시 이탈율 38%)
- API 호출이 너무 많거나 이미지 처리 등 다양한 원인으로 느려질 수 있음
- 번들 사이즈는 그 중 하나
- 자바스크립트는 파일을 다운로드하고 파싱한 후에 컴파일을 하고 실행까지 하는 등 같은 용량이더라도 처리 비용이 크다
- Webpack Analyse는 가장 다양한 정보를 주지만 사용하기 어려움
- Webpack Visualizer는 깔끔한 시각화를 보여주지만 기능이 부족함
- 그래서 Webpack Bundle Analyzer를 추천함
- 번들 용량을 줄일 때 가장 먼저 해야 할 일 : 용량이 큰 라이브러리는 가벼운 라이브러리로, 용도가 겹치는 라이브러리는 하나로 통일
- 여러 라이브러리가 다른 버전의 라이브러리를 참조하는 경우 Dependency confilict가 일어남
- npm은 라이브러리를 트리 구조로 저장하기 때문에 node_modules가 과도하게 커지게 됨
- npm dedupe 명령어와 yarn deduplicate 패키지, yarn 2에선 dedupe 명령어가 생김
- webpack alias기능을 이용한다면 동일한 라이브러리의 중복을 피할 수 있음
- lodash는 기능에 비해 용량이 클 수 있다
- 따라서 가능한 네이티브 함수를 이용하거나 더 가벼운 함수로 구현하여 사용 중임 (참고자료)
- polyfill도 고려할 것
- Bundle Phobia를 통해 버전별 번드 용량, 해당 라이브러리의 디펜던시를 분석 할 수 있다
- 더 가벼운 라이브러리를 만들 때엔 tree-shaking를 고려할 것
- 하지만 tree-shaking은 side effect가 없을 때에만 가능
- terser를 사용하면 terser가
/*#__PURE__**/
를 발견하면 해당 코드에 side effect가 없다고 판단함 (Babel은 이런 pure annotation을 인식하지만 ts는 인식하지 못함) - 무거운 라이브러리의 영향을 줄이기
4. 실무에서 바로 쓰는 Frontend Clean Code
열어보기
- 이 발표에서 소개하는 것
1 |
|
(1) 클린코드가 의미 있는 이유란?
“그 코드는 안건드리시는 게 좋을거에요. 일단 제가 만질게요.^^;;”
→ 흐름 파악이 어렵고, 도메인 맥락 표현이 안 되어 동료에게 물어봐야 알 수 있는 코드
→ 이런 코드는 개발 시 병목되고 유지보수 시 오래 걸리고 심한 경우 기능 추가가 불가능한 상태가 됨
- 실무에서 클린 코드의 의의는 유지보수 시간의 단축을 뜻한다.
(2) 안일한 코드 추가의 함정
- 기존 코드에 기능을 추가하다보면 일어날 수 있는 함정들
1 | function QP() { |
이 코드는…
- 하나의 목적인 코드가 여러 블럭으로 흩어져 있음
- 하나의 함수가 여러가지 일을 하고 있음
- 함수의 세부 구현 단계가 제각각임
그 때는 맞고 지금은 틀리다
1 |
|
- 클린코드란 원하는 로직을 빠르게 찾을 수 있는 코드
(3) 로직을 빠르게 찾을 수 있는 코드
- 응집도: 같은 목적의 코드는 뭉쳐두자
- 무엇을? 당장 몰라도 되는 디테일
- 코드 파악에 필수적인 핵심 정보를 뭉치면 오히려 답답해짐
- 어떻게 응집시킬까?
- 첫번째, 남겨야 할 핵심 데이터와 숨겨야 할 세부 데이터를 나누기
- 두번째, 핵심 데이터는 밖에서 전달, 나머지는 뭉친다
- 이를 선언적 프로그래밍이라 한다
- 그렇다고 선언적인 코드가 무조건 좋은 것은 아니다
- 단일책임: 하나의 일을 하는 뚜렷한 이름의 함수를 만들자
- 일단, 함수 이름을 지어보자 → 함수가 하는 일을 모두 표현할 수 있는 이름을 짓자
- 한 가지 일만 하는 명확한 이름의 함수로 쪼개기
- 비슷한 방식으로 한 가지 일만 하는 기능성 컴포넌트 만들기 (React Hooks)
- 조건이 많아지면 오히려 한글 변수명을 사용하는 게 더 좋을 수도 있다 (마치 주석을 달아놓은 것만 같은 효과도 있음 )
- 추상화: 핵심 개념을 뽑아내자
- 컴포넌트로 추상화하기
1 |
|
- 함수를 추상화하기
1 | // 설계사 라벨을 얻는 함수 구현 |
얼마나 추상화할 것인가?
1 | // Level 0 |
- 꼭 레벨3가 정답이 아님 → 상황에 따라 다름
- 추상화 수준이 섞여있다면 코드 파악이 어려우니 주의할 것
- 액션 아이템
- 담대하게 기존 코드 수정하기: 두려워하지 말고 기존 코드를 씹고 뜯고 맛보고 즐기자
- 큰 그림 보는 연습하기: 그 때는 맞고 지금은 틀리다. 기능 추가 자체는 클린해도 전체적으로는 어지러울 수 있다
- 팀과 함께 공감대 형성하기: 코드에 정답은 없다. 명시적으로 이야기를 하는 시간이 필요하다
- 문서로 적어보기: 글로 적어야 명확해진다. 향후 어떤 점에서 위험할 수 있는지, 어떻게 개선할 수 있는지를 정리해두기
느낀점
- 프론트엔드 엔지니어는 끝없이 더 나은 사용자 경험을 위해 투쟁하는 사람들이다
- 개발에 필요한 스킬만이 중요한 것이 아니다, 결국은 협업, 팀플레이.
- 결국 코드를 적는 일도 글쓰기와 다르지 않다. 누군가에게 잘 읽히는 글을 써야 하는 것.
- 실무 레벨에서 필요한 스킬을 배웠다기 보다는 어떤 마음가짐을 배웠다는 게 더 중요했던 강의들이었다.