React 컴포넌트가 순수해야 하는 이유
·
React
무한 렌더 루프부터 성능 저하, 컨커런트 기능 마비까지 연쇄 문제가 터진다면 React 컴포넌트가 순수하지 않게 작성되었을 가능성이 있습니다. React 컴포넌트의 순수성은 “렌더 안에서는 출력(JSX)만, 렌더 밖에서는 사이드이펙트만”이라는 단순한 원칙을 지키기만 하면 됩니다.아래 내용에서 그 예시와 문제점들을 다루어보겠습니다.순수성이란?순수함수외부 상태를 변경하지 않습니다.(no side effect)동일한 입력에 항상 동일한 출력을 반환합니다.컴포넌트의 순수성React 컴포넌트 함수는 순수함수의 특징을 그대로 가져야 합니다.컴포넌트 함수는 사이드 이펙트에 관여하지 않습니다.그렇다면 사이드이펙트는 누가 담당할까요?DOM 반영(Commit Phase)에서Paint 이전 → useLayoutEffectP..
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..
React Props: 원시값 vs 참조값 비교하기
·
React
React 컴포넌트에 props로 전달하는 값의 타입에 따라 리렌더링 동작이 달라집니다. 원시값 propsfunction Parent() { return ; // 숫자(원시값)} 값 자체를 직접 비교하므로 직관적으로 값이 바뀌었을때만 리렌더링되므로 직관적입니다.참조값 propsfunction Parent() { return ; // 객체(참조값)}참조값은 값 자체가 같더라도 매 렌더링마다 새로운 객체로 생성되기 때문에 다 객체의 주소가 변경되어 다른 값으로 인식됩니다.➡️ props의 내용이 바뀌지 않은 경우에도 자녀 컴포넌트는 불필요하게 리렌더링 되는 문제가 발생합니다.따라서 이에 대응하기 위해 다음과 같은 전략을 사용할 수 있습니다. 1. 변경되지 않는 상수 객체는 컴포넌트 외부에 선언..
React 18 주요 변경점
·
React
Automatic Batching 상태 업데이트(setState)를 하나로 통합해서 배치처리를 한 후 리렌더링을 진행합니다. → 리렌더링 관련 성능 개선 v17 에서는: 이벤트 핸들러 내부에서 발생하는 상태 업데이트 시 fetch()등 과 같은 콜백을 받아 처리하는 메소드가 존재할 경우에는 Automatic Batching이 처리되지 않았습니다. // v17 & v18: 2가지 상태 업데이트가 이루어졌지만 1번의 리렌더링 발생 const onClick = () => { setNumber((prev) => prev + 1); setBoolean((prev) => !prev); }; --- // v17: 2번의 리렌더링 발생 // v18: 1번의 리렌더링 발생 const onClick = () => { //..