useState, useRef, 제어컴포넌트, 비제어컴포넌트

강연주·2025년 9월 26일

📚 TIL

목록 보기
175/186

form은 특이한 태그라서 input, textarea 등은 자기만의 상태를 갖게 된다.
React에서도 상태를 관리하는데, 이걸 React에서 상태로 만들어 변화에 따라 리렌더링을 유발하며 직접 제어하느냐(제어컴포넌트) DOM에 맡기고 리렌더링을 피하느냐(제어컴포넌트)의 두 가지 경우가 있는 것.

event.target.value and ref.current.value are two distinct methods for accessing the value of an HTML input element, primarily within a React context. Their usage and implications differ significantly.


event.target.value 제어컴포넌트

  • event.target.value is used in event handlers, such as onChange for input fields.
  • event.target refers to the specific DOM element on which the event was originally dispatched.
  • When an event like onChange fires on an input, event.target will be that input element, and event.target.value will provide its current value at the time of the event.
  • This is the standard and preferred method for handling input values in React's "controlled components" pattern, where component state directly manages the input's value.

ref.current.value 비제어컴포넌트

  • ref.current.value is used when a React ref is attached to a DOM element, typically an input element.
  • A ref provides a direct reference to the underlying DOM node or React component instance.
    ref.current accesses the actual DOM element (e.g., an HTML input element) that the ref is attached to.
  • ref.current.value then retrieves the current value of that DOM element directly.
  • Refs are generally used for "uncontrolled components" or when direct DOM manipulation is necessary, such as managing focus, triggering animations, or integrating with third-party DOM libraries.
  • Updating a ref's current value does not trigger a re-render of the component, unlike updating component state.

Key differences

Trigger

event.target.value is accessed within an event handler, while ref.current.value can be accessed at any point after the ref is initialized and the component has rendered.

Purpose

event.target.value is for capturing real-time changes in user input for controlled components, whereas ref.current.value is for direct DOM access, often for uncontrolled components or specific DOM interactions.

Re-renders

Using event.target.value in conjunction with setState will typically trigger re-renders. Accessing ref.current.value directly does not cause re-renders.

Best Practice

For most form inputs in React, event.target.value combined with controlled components is the recommended approach for managing input state and ensuring a predictable UI. Refs should be used sparingly and for specific use cases where direct DOM interaction is required.


State + Ref 혼합 사용

장점

  • 성능 최적화: nameRef는 타이핑할 때마다 리렌더링을 유발하지 않음
  • 글자수만 반응적: nameLength state만 업데이트되어 UI 갱신
  • 제출 시점에만 값 읽기: ref로 최종값을 한 번만 읽어서 효율적

단점

  • 복잡도 증가: state와 ref 두 가지 패턴 혼재
  • 동기화 이슈 가능성: state와 ref 값이 따로 관리됨
  • 내 코드는 setNameLength 때문에 리렌더링도 유발되어 장점X

현재 코드는 "ref의 리렌더링 방지 효과"는 못 살리지만,
"value를 state로 안 올리는 효과"는 얻고 있습니다.
완전히 리렌더링을 막으려면 글자수 표시를 포기해야 합니다.

"그럼, 리렌더링 횟수는 똑같이 일어나더라도, state 개수가 줄어드는 게 좋은 이유는 뭐야? 그리고 이 이유가, 'useRef should be sparingly used'라는 문구를 상쇄할 만큼 유의미해?"

솔직히 말하면, 이 경우에는 state 개수를 줄이는 것이 큰 의미가 없습니다. 오히려 코드가 더 복잡해지는 단점이 있습니다.

State를 줄여도 별 이득이 없는 이유
1. 리렌더링은 똑같이 발생
typescript// 현재 방식 (ref + state)
setNameLength(value.length) // 리렌더링 1회

// state만 쓰는 방식
setName(value) // 리렌더링 1회
성능상 차이 전혀 없음.

  1. 메모리 차이 미미

nameLength (number) vs name (string)
실제 메모리 차이는 무시할 수준

  1. 코드 복잡도 증가
typescript
// ref + state: 두 가지 패턴 혼재
const nameRef = useRef<HTMLInputElement>(null);
const [nameLength, setNameLength] = useState(0);
// 제출 시: nameRef.current?.value

// state만: 일관된 패턴
const [name, setName] = useState("");
// 제출 시: name
State만 쓰는 게 더 명확하고 React스러움.
"Ref는 sparingly use" 원칙
React 공식 문서의 의도는:
Ref를 써야 할 때:

DOM 직접 조작 (focus, scroll, 애니메이션)
리렌더링 없이 값 저장 (타이머 ID, 이전 값 추적)
외부 라이브러리 통합

Ref를 피해야 할 때:

폼 입력 관리 ← 당신의 경우
UI에 표시되는 데이터
React가 추적해야 하는 상태

결론: 당신의 코드는 개선이 필요합니다
현재 방식은 "ref의 장점도 못 살리면서" 복잡도만 높입니다.


네, 렌더링 횟수를 줄이면 성능에 도움이 됩니다. 하지만 실제 효과는 상황에 따라 다릅니다.
렌더링 최적화의 성능 효과
이론적 이득:
typescript// 현재: 타이핑할 때마다 리렌더링
"a" 입력 → 리렌더링
"ab" 입력 → 리렌더링
"abc" 입력 → 리렌더링
// 3글자 = 3번 리렌더링

// 최적화: 리렌더링 없음
"abc" 입력 → 리렌더링 0번
각 리렌더링마다 일어나는 작업:

Virtual DOM 재생성
Diffing 알고리즘 실행
하위 컴포넌트들 재평가
useEffect, useMemo 등 재실행

실제 성능 측정
하지만 당신의 폼 컴포넌트 수준에서는 차이가 거의 없습니다:
측정해보면:
typescriptconst start = performance.now();
// 리렌더링 발생
const end = performance.now();
console.log(렌더링 시간: ${end - start}ms); // 보통 0.1~1ms
이유:

컴포넌트가 상대적으로 단순함
하위 컴포넌트가 많지 않음
복잡한 계산이나 네트워크 요청 없음

언제 렌더링 최적화가 중요한가
의미 있는 경우:
typescript// 1. 대용량 리스트
{items.map(item => )} // 1000개 아이템

// 2. 복잡한 계산
const expensiveValue = useMemo(() => {
return heavyCalculation(data); // 100ms 소요
}, [data]);

// 3. 깊은 컴포넌트 트리


{/ 50개 레벨 /}
의미 없는 경우 (당신의 상황):

단순한 폼 컴포넌트
몇 개 안 되는 input 필드
복잡한 자식 컴포넌트 없음

결론
렌더링 횟수를 줄이는 것은 이론적으로는 항상 좋습니다. 하지만:

당신의 폼에서는 체감할 수 있는 성능 향상이 없음
코드 복잡도 증가의 단점이 더 큼
유지보수성과 가독성이 더 중요함

"premature optimization is the root of all evil" 원칙에 따라, 실제 성능 병목이 발생하기 전까지는 명확하고 유지보수하기 쉬운 코드가 우선입니다.
당신의 경우 controlled component (state만 사용)가 가장 적절한 선택입니다.


  • 반복적 리렌더링은 실제로 얼마나 많은 부하인가?
  • 폼에 ref를 사용하는 것이 (focus, file 등의 경우는 권장) React 원칙에 위배되는가?
  • RHF 리팩토링은 얼마나 많은 공수가 들며 얼마나 효과적인가?

  • 모임생성/수정 폼
    • RHF?
    • 모임 등록 버튼 disabled?
    • required랑 유효성 통과 못할 시 focus?
    • 날짜 유효성 검사는 제출시 경고가 뜨는데 입력시(state실시간 비교)로 바꿔야 하나?
profile
아무튼, 개발자

0개의 댓글