Type Object 프론트에서 써먹기
·
<frontend>/타입 | 도메인
들어가며저는 불가능한 상태를 타입으로 못 만들게 하는 걸 좋아합니다. 근데 옵션끼리 서로 얽혀 있고 팔 수 있는 조합이 수시로 바뀌는 상품 카탈로그를 설계하다가, 이 기법이 오히려 발목을 잡는 경우를 만났습니다. 실제 도메인(유전변이 해석)은 낯서니까, 구조가 놀랍도록 비슷한 애플스토어 맥북 견적 화면으로 바꿔서 이야기하겠습니다.맥북은 조합으로 팝니다애플은 맥북을 한 덩어리로 팔지 않습니다. 네오, 에어, 프로 세 라인이 있고, 같은 라인 안에서도 화면·칩·메모리·저장장치·색상이 다르면 가격이 다른 별개 상품입니다.조건 값을 전부 곱하면 이렇습니다.라인 3 × 화면 4 × 칩 4 × 메모리 6× 저장장치 5 × 색상 8 × 텍스처 2= 이론상 23,040가지물론 실제로 팔리는 건 그중 일부입니다. 옵션끼..
closure of operations(닫힌 연산) FE 인가정책에 적용하기
·
<frontend>/타입 | 도메인
유저 권한 조건이 복잡하게 조합되는 Domain Driven Development에 나오는 닫힌 연산이라는 개념으로 설명해보겠습니다.닫힌 연산이 뭔가요에릭 에반스의 『도메인 주도 설계(DDD)』에 나오는 패턴입니다.Where it fits, define an operation whose return type is the same as the type of its argument(s).맞는 자리라면, 반환 타입이 인자 타입과 같은 연산을 정의하라.넣는 것과 나오는 것의 타입이 같은 연산이라는 뜻입니다. 정수 덧셈이 딱 그렇습니다.1 + 2 // 3(1 + 2) + 3 // 6((1 + 2) + 3) + 4 // 10정수를 넣으면 정수가 나오니까 결과를 다시 덧셈에 넣을..
데이터 오염을 경계에 격리하기
·
<frontend>/컴포넌트 설계
들어가며 기능 개발을 하다 보면 이런 공지를 받습니다. "API 응답에 필드 하나 추가했어요. 옵셔널이라 기존 동작엔 영향 없습니다."저희는 파이프라인이 복잡하고 오래 운영된 서비스라 레거시 데이터가 많았고, backfill(나중에 생긴 규칙을 과거 데이터에 채워넣는 DB 작업)보다 API 응답 옵셔널 추가가 합리적인 상황이 자주 나왔어요. 그래서 이런 공지도 자주 받았고, 매번 별생각 없이 넘겼습니다.근데 몇 주 지나서 컴포넌트 코드를 열어보면, 그 영향 없는 필드가 여기저기 분기를 뿌려놓고 있더라고요.옵셔널인데 왜 문제가 생겨요?실제로 동료가 물었던 질문입니다. 옵셔널 추가는 API 스펙 관점에선 하위호환이 맞아요. 문제는 스펙이 아니라 타입이 앱 안에서 하는 일입니다. 서버가 필드를 옵셔널로 추가하..
서버 상태가 바뀌는 스텝은 라우트를 쪼갠다
·
<frontend>/컴포넌트 설계
들어가며회원가입이나 결제처럼 스텝이 이어지는 UI는 겉보기엔 다 비슷하지만 두 종류가 있습니다. 정보를 다 모은 뒤 마지막에 한 번만 서버 상태를 바꾸는 퍼널, 그리고 스텝마다 서버 상태가 바뀌는 플로우입니다. 퍼널은 한 라우트 안에서 useState로 만들어도 되지만, 스텝마다 서버 상태가 바뀌면 라우트도 그 지점에서 쪼개야 합니다. 둘이 어떻게 다른지, 섞여 있을 땐 어떻게 나누는지 MFA(2차인증) 설정 화면 예시로 정리해보겠습니다.퍼널은 마지막에 한 번만 서버 상태를 바꾼다이메일 입력 → 인증 코드 확인 → 프로필 입력처럼 스텝이 순서대로 이어지는 화면을 흔히 퍼널이라고 부릅니다. 보통 이렇게 만듭니다.type SignUpStep = | "email" | "verify" | "profile"..
구현 말고 질문에 이름 붙히기
·
<frontend>/타입 | 도메인
들어가며여러 종류의 아이템을 다루다 겪은 일인데, 실제 도메인은 좀 낯서니까 커머스 대시보드 예시로 바꿔서 이야기해보겠습니다.활동 내역을 다루는 화면을 만들고 있었습니다. 활동에는 여러 종류가 있고, 각 종류마다 선택 상태를 key로 관리해요. 주문(order)을 예로 들면 key 구조가 이렇습니다.order key: userId+orderIdline key: userId+orderId::skuline key가 order key를 prefix로 포함하는 구조입니다. 그래서 "이 아이템이 이 주문에 속하는가"를 startsWith 하나로 판별할 수 있어요.const isChecked = selected.some(item => orderList.some(order => item.key.startsW..
토스 Frontend Fundamentals 모의고사 후기 (2회차)
·
<frontend>/toss ff 모의고사
1회차 후기에 이어서 2회차 모의고사 후기를 쓴다.1회차에서도 강조됐던 내용인데, 예측 가능한 코드를 짜야한다는주제가 이번에도 반복적으로 나왔다사람 뇌는 코드를 "이해"하지 않는다사람 뇌가 코드를 읽는 방식은 생각보다 단순하다. 한 줄 한 줄 의미를 분석하는 게 아니라, 함수명이나 구조를 보고 "이렇게 동작하겠지?"를 먼저 예측한다. 그 예측이 맞으면 넘어가고, 틀리면 그때서야 내부를 파고든다.문제는 예측이 틀렸을 때다. 인지 강도가 급격하게 올라간다. 결국 가독성이라는 건 코드가 얼마나 "뻔하게" 읽히느냐의 문제다.호출처의 관심사를 모르게 추상화한다이번에 가장 신선했던 관점이다.DatePicker 컴포넌트를 하나 만든다고 생각해보자. 이 컴포넌트에 date와 setDate를 prop으로 내려보내면 어떻..
원자적 action으로 훅의 확장성 확보하기
·
<frontend>/컴포넌트 설계
들어가며vscode의 탭 바처럼 동작하는 대시보드를 만들고 있었습니다. 파일을 열면 탭이 생기고, 클릭하면 전환되고, X로 닫는 그 구조요.대시보드에는 화면 종류가 몇 가지 있습니다. 항상 존재하는 기본 탭인 home이 있고, 새 인사이트를 작성하는 new는 사이드바에서 "새 페이지 만들기"를 누르면 열립니다. 작성이 끝난 개별 인사이트 페이지는 insight-{id}인데, new에서 저장하면 insight-42 같은 탭으로 교체됩니다. 그 밖에 특정 태그에 속한 인사이트 목록 tag-{id}, 그리고 휴지통 trash가 있습니다.이 탭 상태를 어디에 저장할까요? React state에 넣으면 간단하지만 새로고침하면 날아갑니다. 그래서 공유 기능도 넣을 겸 URL query string을 single s..
Branded Type String Key Factory 만들기
·
<frontend>/타입 | 도메인
들어가며지난 글에서 Branded Type으로 유틸 함수 사용을 강제하는 방법을 다뤘습니다. 지난 글과 마찬가지로 실제 도메인은 낯서니까 커머스 대시보드 예시로 바꿔서 이어가겠습니다. getOrderAnchorId, getRefundAnchorId 같은 개별 함수를 만들어서 activity type마다 올바른 ID 포맷을 보장했죠.근데 activity type이 6개가 넘어가면서 함수가 너무 많아졌고, 어떤 게 있는지 찾기가 힘들어졌습니다. 자동완성으로 get만 쳐도 수십 개가 뜨는데, 그 중에서 anchor id 관련 함수만 골라내는 것도 일이었거든요.마침 예전에 쓰던 쿼리 키 팩토리 패턴이 떠올랐습니다. 쿼리 키처럼 일괄 invalidate할 일은 없지만, 관련 함수를 하나의 객체로 묶는다는 아이디어..
Branded Type으로 유틸함수 강제하기
·
<frontend>/타입 | 도메인
들어가며유전진단 SaaS에서 여러 종류의 아이템을 다루다 겪은 일인데, 실제 도메인은 좀 낯서니까 커머스 대시보드 예시로 바꿔서 이야기해보겠습니다.대시보드에서 활동 내역(activity) 아이템을 클릭하면 해당 row로 스크롤되는 기능이 있었습니다. URL 해시 기반 네비게이션인데, 사용자 입장에서의 흐름은 이렇습니다.테이블 row에는 id="ORDER----" 속성이 붙어 있고(strategy), activity card에서 코멘트 버튼을 누르면 "ORDER----"가 포함된 메시지가 채팅에 추가됩니다(message). 나중에 채팅에서 그 메시지의 "Go to Activity" 링크를 누르면 #ORDER---- 해시로 이동해서 row로 스크롤되고요(navigation).row에 부여한 ID를 메시지에 ..
컴포넌트 분기를 상위로 밀어내기
·
<frontend>/컴포넌트 설계
들어가며이전 글에서 로직 계층을 내려서 단일 관심사를 챙기는 이야기를 했었습니다. 여러 주제가 섞인 컴포넌트를 분리하고, 각 컴포넌트가 자기 로직만 가지도록 만드는 거였죠.이번엔 반대 방향입니다. 분기를 상위로 올려서 단일 관심사를 챙기는 이야기를 해보겠습니다. 여기서 단일 관심사란 곧 높은 응집도, 그러니까 하나의 컴포넌트가 하나의 맥락에 필요한 것만 모아 둔 상태를 말합니다. 근데 컴포넌트 안에서 강하게 결합된 로직들을 어떻게 분리할 수 있을까요?위에서 풀수록 아래가 단순해집니다React 컴포넌트는 단방향 트리 구조입니다. 데이터와 제어 흐름이 위에서 아래로만 흐르죠. // 여기서 결정하면 // 여기는 단순해진다즉 상위에서 복잡도를 해소할수록 하위가 단순해집니다. Typ..
단일 관심사 컴포넌트
·
<frontend>/컴포넌트 설계
들어가며이전 글에서 훅 추상화를 아토믹하게 쪼개는 이야기를 했었습니다. 몬스터 훅 하나 대신 URL 필터 동기화, 날짜 범위 처리 같은 로직을 각각 분리해서 조합해 쓰는 방식이었죠. 덕분에 각 로직이 한 가지 일만 해서 파악하기 쉬웠습니다.근데 막상 컴포넌트를 보면 얘기가 좀 달랐습니다. 상품 상세 페이지를 만든다고 해보겠습니다.function ProductPage({ productId }) { // 상품 정보 관련 const { data: product } = useProductQuery(productId); // 장바구니 관련 const [quantity, setQuantity] = useState(1); const { mutate: addToCart } = useAddToCart(); /..
토스 Frontend Fundamentals 모의고사 후기
·
<frontend>/toss ff 모의고사
토스 팀에서 공개한 Frontend Fundamentals라는 코드 작성 지침서를 기반해 이걸 기반으로 토스 채용 과제를 다같이 풀어보는 모의고사를 진행했다.각자 과제를 풀고 PR을 올려 서로 코드 리뷰를 했고, GitHub Discussion에서 설계 철학을 토론했다. 그리고 유튜브 라이브 방송을 통해 토스 리드 개발자분들의 관점도 들을 수 있었는데, 질의응답 시간이 특히 유익했다.그냥 넘기기엔 아까운 인사이트들이 많아서 정리해본다.Suspense에 대한 관점 전환지금까지 나는 Suspense를 "로딩 fallback UI 보여주는 용도"로만 생각했다. 필요한 곳에만 선택적으로 쓰는 것처럼.근데 이번에 완전히 관점이 바뀌었다:Suspense는 성공 케이스만 신경쓸 수 있게 해주는 깔대기다.n개의 비동기..