React 컴포넌트가 순수해야 하는 이유
·
React
무한 렌더 루프부터 성능 저하, 컨커런트 기능 마비까지 연쇄 문제가 터진다면 React 컴포넌트가 순수하지 않게 작성되었을 가능성이 있습니다. React 컴포넌트의 순수성은 “렌더 안에서는 출력(JSX)만, 렌더 밖에서는 사이드이펙트만”이라는 단순한 원칙을 지키기만 하면 됩니다.아래 내용에서 그 예시와 문제점들을 다루어보겠습니다.순수성이란?순수함수외부 상태를 변경하지 않습니다.(no side effect)동일한 입력에 항상 동일한 출력을 반환합니다.컴포넌트의 순수성React 컴포넌트 함수는 순수함수의 특징을 그대로 가져야 합니다.컴포넌트 함수는 사이드 이펙트에 관여하지 않습니다.그렇다면 사이드이펙트는 누가 담당할까요?DOM 반영(Commit Phase)에서Paint 이전 → useLayoutEffectP..
Zustand 셀렉터... 사용하고 계시죠? (리렌더 폭탄 방지하기)
·
React
공식 홈페이지의 얕은 함정Zustand 공식 홈페이지에 들어가면 가장 먼저 보이는 예제입니다.import { create } from 'zustand'const useStore = create((set) => ({ count: 1, inc: () => set((s) => ({ count: s.count + 1 })),}))function Counter() { const { count, inc } = useStore() return ( {count} one up )}물론 이 코드를 그대로 복사해도 문제없이 잘 돌아가는듯 합니다.그러나 프로젝트가 커지면서 '원인 모를 리렌더'가 빈번히 발생하고,추적끝에 Zustand를 사용하면서 발생한 문제라는 것을 알게됩니다. 셀렉..
React Core 구현하기 - 2. JSX 런타임 로직
·
React
JSX 런타임은 트랜스파일러가 생성한 JSX 객체를 VDOM으로 변환하는 역할을 합니다.JSX 런타임 로직 구현JSX 트랜스파일을 위한 설정이 완료되었다면, 트랜스파일러가 사용할 JSX 변환 로직을 구현합니다.파일명은 jsx-dev-runtime.ts으로 생성합니다. 이는 트랜스파일 설정에서 runtime: "automatic" 시 기본적으로 해당 파일명을 찾기 때문입니다.(prod 환경에서는 jsx-runtime.ts를 사용하지만 이 프로젝트에서는 사용하지 않습니다.)  기본 구조// jsx-dev-runtime.tsconst flattenChildren = (children?: TVDOMType): TVDOMType[] => { ...};export const jsxDEV = (type: TVDOM..
React Core 구현하기 - 1. JSX 컴파일
·
React
* 본 프로젝트는 Vite + VanilaJS로 생성되었으며, TypeScript를 사용합니다.* 분량조절을 위해 타이핑 관련 코드작성은 생략합니다.사용된 패키지@babel/core: Babel이 동작하기 위한 엔진@babel/cli: Babel을 커맨드라인에서 사용할 수 있게함@babel/plugin-transform-react-jsx: JSX를 JavaScript로 변환@babel/preset-typescript: TypeScript를 JavaScript로 변환 JSX는 JavaScript의 확장 문법으로, HTML과 비슷한 구조를 사용해 웹 화면을 쉽게 구성할 수 있게 해줍니다.그러나 JSX는 브라우저가 직접 이해할 수 없는 문법이므로, JavaScript로 변환하는 과정(트랜스파일)이 필요합니다...
React18 useEffectEvent
·
React
기존 useEffect에서의 외부 함수 호출외부함수가 일반함수일 때: 의존성 배열에 함수를 포함하지 않으나 외부함수에 사용되는 디펜던시를 포함function logSearch(results) { console.log(`검색어: ${keyword}, 필터: ${filters}, 결과 수: ${results.length}`);}useEffect(() => { fetchResults(keyword).then(results => { logSearch(results); });}, [keyword, filters]);filters가 변경될 때마다 effect가 실행되므로 의도되지 않은 API 호출 발생filters를 의존성 배열에서 제거하면 문제를 해결할 수 있지만 ESLint 경고 발생하므로 ..
React useLayoutEffect
·
React
useEffect와 useLayoutEffect의 실행 시점function App() { const [count, setCount] = useState(0) useEffect(() => { console.log('useEffect', count) }, [count]) useLayoutEffect(() => { console.log('useLayoutEffect', count) }, [count]) function handleClick() { setCount((prev) => prev + 1) } return ( {count} )} 리액트가 DOM을 업데이트useLayoutEffect 실행(동기적)useLayoutEffect의 실행이 종료될 때까지 기다렸다가 페인팅오래걸..
React forwardRef와 useImperativeHandle
·
React
forwardRef개념부모 컴포넌트에서 자식 컴포넌트로 ref를 전달할 수 있게 해주는 React 기능입니다. 기본적인 사용법// ref를 props로 받으로면 컴포넌트 함수를 forwardRef로 감싸야 합니다.const ChildComponent = forwardRef((props, ref) => { return ...})function ParentComponent() { const childRef = useRef(null) // 속성명 ref로 props를 넘겨줍니다. return } 주요 사용 사례폼 요소 제어 (포커스, 값 검증)애니메이션 제어스크롤 위치 조작미디어 요소 제어 (비디오/오디오 플레이어)useImperativeHandle개념부모에게서 넘겨받은 ref를 원하는대로 수정할 수 ..
리액트 파이버의 동작
·
React
파이버란?리액트의 재조정(reconciliation) 엔진으로써의 의미React 컴포넌트에 대한 정보를 담는 자료구조로써의 의미 파이버 이전(~리액트 15) 스택 알고리즘의 한계작업이 동기적으로 이루어져, 스택이 빌때까지 중단불가 → 리액트의 비효율성으로 이어짐.ex) 자동완성 기능이 있는 검색 인풋의 경우 인풋 입력과 api 노출이 동시에 이루어질 수 없어 인풋 입력이 버벅이는 현상 발생 파이버의 작업 단계(리액트 16 ~)렌더단계VDOM을 업데이트하는 단계비동기실행작업의 우선순위 설정 및 중단,재시작, 폐기 수행렌더단계커밋단계DOM에 실제 변경사항 반영동기실행중단 없이 처리실제 리액트 코드에서 구현되어있는 파이버 살펴보기function FiberNode(tag, pendingProps, key, mo..
리액트 Hooks와 클로저
·
React
useState는 컴포넌트 내부 로직에서 호출되고 종료되는 함수입니다. 그런데 setState는 어떻게 useState 내부의 state 최신값을 계속해서 확인할 수 있을까요?클로저가 useState 내부에서 활용되기 때문입니다. React의 Hooks는 이러한 클로저를 기반으로 만들어져 있으니, 각 Hook이 어떻게 클로저를 활용하는지 자세히 알아보겠습니다. useStateconst [count, setCount] = useState(0);return ( setCount(count + 1)}> {count} );// ❌ 외부에서 직접 수정 불가count = 100;// ✅ 오직 setter 함수로만 수정 가능setCount(100); useEffectuseEffect(() =>..
useEffect 사용시 주의해야 할 것들
·
React
자제해야 하는 케이스 Props에 기반한 상태 업데이트// 🚫 잘못된 사용function ProductCard({ price, quantity }) { const [total, setTotal] = useState(0); useEffect(() => { setTotal(price * quantity); }, [price, quantity]); return 총 가격: {total};}// ✅ 개선: 렌더링 중에 직접 계산function ProductCard({ price, quantity }) { const total = price * quantity; return 총 가격: {total};} Props를 State로 미러링// 🚫 잘못된 사용function..