[3팀 이호재] Chapter 1-3 React, Beyond the Basics #55
Open
kevinhojae wants to merge 25 commits into
Open
Conversation
hanbeulYou
reviewed
Apr 10, 2025
Comment on lines
+17
to
+18
| <AuthProvider> | ||
| <Header /> |
There was a problem hiding this comment.
개인적으로 궁금한 부분인데, Auth를 Layout단에 따로 넣은 이유가 있나용?
Author
There was a problem hiding this comment.
아 auth 기능을 header에서만 사용하고 있어서 컨텍스트 범위를 최소화하기 위한 목적이었습니다!
사실 auth 기능은 일반적으로 앱 전반적으로 필요해서 provider를 전체에 감싸줘야 할텐데, 여기서는 Header 컴포넌트에서만 쓰이고 있길래 Provider 범위 줄이려고 Header에만 감싸줬어요
|
지금 보니까 진짜 미쳤네요; 휴 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
과제 체크포인트
배포 링크
https://kevinhojae.github.io/front_5th_chapter1-3/
기본 과제 checklist
심화 과제 checklist
과제 셀프회고
🤔 Design consideration and decisions
#useRef 구현
과제를 시작할 때, useRef 구현시 useState를 사용하라고 힌트를 받았다.
왜 useState를 사용해야 하고, 다른 구현 방식을 사용하지 않는 이유가 뭘까? 궁금해졌다.
useState 외 구현 접근
approach 1. 상수를 이용해서 구현하기
컴포넌트 함수 내부에서 단순 객체 선언:
const ref = { current: initialValue }컴포넌트가 리렌더링될 때마다 함수가 재실행되면서 새로운 객체가 생성됨
참조가 깨지고 값이 초기화되어 useRef의 핵심인 렌더링 간 값 유지가 불가능
approach 2. 외부 변수를 활용한 구현
const refStore = new Map();를 두고 ref에 따라 값 업데이트하는 식으로도 생각해볼 수 있음하지만 React의 생명주기와 동기화되지 않음. 컴포넌트가 unmount되어도 refStore에는 데이터가 남아있고, 클린업 관리가 어려움
왜 useState을 사용해야 할까?
결론부터 말하면, useRef의 값을 react fiber 메모리에 묶어두기 위함이다.
useState를 사용해서 상태 값을 정의하게 되면 두 가지를 보장한다.
React의 Hook linked list 시스템에 등록된다 → fiber의
memoizedState에 연결됨생성된 상태 객체는 React가 관리하는 메모리 상에 저장된다 → 렌더링이 반복되어도 React가 해당 순서의 Hook의 값을 기억하고 있어서 초기화되지 않음
useRef를 사용하는 목적은 저장된 값은 유지하지만 리렌더링을 유발하지 않기 위함이다.
#FSD architecture 적용
항상 적용해보고 싶었던 feature-sliced design 아키텍쳐를 이번 프로젝트에 리팩토링하며 적용해보았다. 기본 layer인
app / features / pages / shared / widgets로 구분지었고, slice에는 관심사별로 클러스팅하여 구분지었다. 그리고 segment로model에는 비즈니스 로직을,ui에 도메인 컴포넌트를 두었다.refs:
⚒️ What I learned
#
useStatedeep diveMotivation
처음에는 useMemo, useRef와 마찬가지로 useState 자체를 구현해보고 싶었으나, useState를 구현하는 의의는 상태 업데이트에 따른 리렌더링 과정을 구현하는데 있다고 생각했다. 하지만 리렌더링 프로세스는 react의 fiber 구조와 너무 깊게 엮여있어서 멈추었다.
리렌더링 없는 useState 구현은 값 또는 함수 전달받아서 내부 상태 업데이트해주는 로직을 위주로 구현하는 태스크일 것 같아 useState의 내부 동작 원리를 deep dive 해보기로 방향을 잡았다.
구현 동작은 react/packages/react-reconciler/src/ReactFiberHooks.js at main · facebook/react 코드를 기반으로 디깅해보았다.
🚀🚀🚀
Under the hood
Mount / Update / Rerender로 분리
React는 useState를 3가지 상황에 따라 다르게 처리한다.
mountState: 컴포넌트가 처음 렌더링될 때 상태 초기화updateState: 상태 업데이트 후 다시 렌더링될 때rerenderState: 렌더링 중 상태가 업데이트될 때 (렌더링 중 다시setState)이들은 각각 상황에 맞는 dispatcher을 통해서 처리된다.
How it works
mountState - 초기 상태 생성 및 fiber와 연결
Implementation (go to mountStateImpl)
mountWorkInProgressHook()을 호출해서 새로운 Hook 객체 생성전달받은
initialState가 함수라면 호출해서 초기값 계산1번에서 생성한 hook 객체의
memoizedState,baseState에 초기값 저장hook.queue에 이후 업데이트를 위한 queue 구조 생성상태 변경 함수 (
dispatchSetState) 생성 후queue.dispatch에 저장Insights
mountWorkInProgressHook(a)새로운 hook을 할당하면서 현재 렌더링 중인 Fiber에 연결시켜주는 hook 연결리스트 생성기
컴포넌트 함수가 useState를 호출할 때마다 hook 객체가 생성되고,
Hook: 상태 저장과 업데이트 추적을 위한 linked list 기반 타입
생성된 hook들이 현재 렌더링 중인 Fiber의
memoizedState리스트로 연결된다.lazy initialiation (b)
Lazy initial state (React docs) 에도 lazy initialization 에 대한 내용이 명시되어 있다. 주요 요지는 useState의 초기값으로 함수를 전달해주면 초기 렌더링에서만 실행되어 lazy initialization이 된다는 것이다.
그 이유를 mountState 구현체에서 찾을 수 있었다.
mountState에서 initialState가 function 타입이면 전달받은 함수를 실행해서 초기값을 설정하는 로직이 있다.
위 예시에서의 차이를 설명하자면,
compute()의 호출 시점의 차이에 있다.useState(compute())컴포넌트가 렌더링될 때마다 compute() 가 즉시 실행되어 리액트가 useState를 처리하기 전에 이미 계산이 완료된다
useState(() => compute()) // lazy initilization함수 자체가 useState가 전달되어 바로 실행되지 않고, 실제로 hook이 마운트될 때
mountState를 통해 함수가 실행된다결국 연산 비용이 큰 로직이면 lazy initialization을 쓰는게 이득인 근본적인 이유를 알 수 있었다.
dispatchSetState(e)setState(새로운 값)를 호출했을 때 업데이트 객체를 생성하고 큐에 넣는 함수로,dispatchSetState는fiber와queue가 바인딩된 상태로 생성됨그리고
dispatchSetStateInternal에서업데이트 객체 생성
큐에 업데이트 객체 추가
React에게 다시 렌더링 요청
이 호출로 인해 React는 해당 컴포넌트를 다음 렌더링 사이클에 포함시킴
updateState - 상태 업데이트 처리 (표면적 흐름만 이해)
Implementation (go to updateReducerImpl)
업데이트 queue 프로세싱
queue.pending을 현재 처리중인baseQueue에 병합업데이트 순회하며 실행
각 업데이트의
action을 순회하면서 reducer를 통해 새로운 상태를 계산우선순위가 맞지 않으면 스킵하고, 다음 렌더링을 위해 복제
업데이트된 상태 저장
hook.memoizedState에 최종 업데이트된 상태 저장hook.baseState및hook.baseQueue업데이트queue.lastRenderedState업데이트이후 렌더링 스케쥴링 로직이 대해 더 깊게 이해해보고 싶다.
rerenderReducer - render phase update
같은 렌더 사이클에서
setState호출 시 실행queue.pending을 처리하여 렌더링 중 상태 업데이트를 가능하게 함최종적으로
memoizedState를 최신 상태로 갱신refs:
리뷰 받고 싶은 내용
fsd 아키텍쳐
entities
지금 저는 인증 관련 (features/auth), 알림 관련 (features/notification), 프로덕트 관련 (features/product) 이 사용자 행동과 데이터 변경인 기능이라고 생각하여 features로 분리했습니다.
그런데 이 중 product는 사용자 행동에 따른 아이템 추가가 일어나는 feature이라고 볼 수도 있지만, 프로덕트 자체는 데이터이니 entities라고 볼 수도 있을 것 같은데, 어떤 분리가 적절할까요?
아직 layer 계층에서 features와 entities가 특히 헷갈려서 어떤 기준으로 분리하는걸 생각하면 좋을지 여쭤보고 싶습니다!
type 정의
만약 type을 여러 곳에 걸쳐서 사용해야 하는 경우라면?
type들이 한 곳에 모여있지 않아서 불편하지 않을지?
fsd 공식 문서의 지침에 따라, type을 공통화해서 관리하지 않고 각 model에서 타입이 사용되는 곳에서 정의했습니다.
그런데 이렇게 되니 드는 의문점이,
type 정의들이 모여있지 않아서 불편하지 않을지?
그 동안 아래와 같은 패턴으로 type을 폴더 하나 위치에 정의해서 사용했었습니다.
만약 type을 정의해야 하는 관심사가 한 파일이 아니라 여러 파일이라고 생각되면 어디에 정의해야 할지?
예를 들어 type Canvas를 정의해야 하는 상황이고 model 폴더에 useCanvasDraw, useCanvasClick 훅이 있을 때 Canvas 타입은 두 훅 모두에게 해당되는데, 어디에 정의해야 할지?
커스텀 훅 내부에서 useContext 사용 vs 커스텀 훅 외부에서 useContext 사용 후 prop 전달
컴포넌트와 같이 커스텀 훅이 호출되는 곳에서 useContext를 사용하고, 커스텀 훅에 의존성으로 주입받는 패턴이 더 명시적으로 드러나서 좋을지?
아니면 prop을 줄이면서 커스텀 훅 자체를 독립적으로 유지하기 위해 useContext를 커스텀 훅 내부에 사용하는게 좋을지?
지금까지는 커스텀 훅 자체를 독립적으로 유지하기 위한 목적으로 useContext를 커스텀 훅 내부에서 호출해왔었는데, 사실 그렇게 되면 커스텀 훅을 사용하는 위치가 무조건 provider로 감싸져야 한다는 implicit한 의존성이 존재해서 더 좋은 패턴이 무엇인지 여쭤보고 싶습니다!