React 의 특장점, 렌더 라이프사이클 및 Hook(6) - Ref

ASAC 웹 풀스택
2024. 9. 22. 11:05
목차
  1. Ref: 외부변수 -> 비제어 컴포넌트
  2. Reference(useRef로 생성한)를 HTML 요소에 연결
  3. Ref는 언제 활용하는가
  4. ✅ 1. 리렌더링 감소
  5. ✅ 2. 순수 HTML 요소 간접 조작
  6. 리액트에서 이런 `ref`를 통해 HTML에 직접 접근해서 무엇을 할 수 있나?
  7. 1. 특정 요소에 포커스
  8. 2. 특정 요소에 Utterances 를 붙일 때
  9. 3.  특정 요소에 Observer붙일 때 (스크롤에 따라 상단에 제목을 표기할지 말지..)
  10. useRef와 setState들이 복잡하게 얽혀있을 때 어떻게 리렌더 영향을 주나?
  11. forwardRef 써야 ref값을 넘길 수 있다는건 인지했는데, 함수명을 매번 바꿔줘야하나?
  12. 익명 함수 컴포넌트를 forwardRef로 감싼 경우
  13. 기명 함수 컴포넌트를 forwardRef로 감싼 경우
  14. 리액트19 버전부터 함수형 컴포넌트에서 Props에 기본으로 ref가 들어감
728x90

Ref: 외부변수 -> 비제어 컴포넌트

비제어 컴포넌트: 리액트가 인지하지 못한 변수이기에 Ref 내부 값이 변경 시 리렌더링 X

 

리액트 리렌더링을 발생시키지 않고 변경 가능한 값을 담는 참조(reference) 생성

  • Ref(외부 변수)를 리액트가 인지할 수 없기에 Ref값의 변동과 리렌더링은 전혀 상관이 없다.라는 의미에서 Ref를 통해 값을 관리하는 컴포넌트를 비제어 컴포넌트라고 부른다.

Reference(useRef로 생성한)를 HTML 요소에 연결

Reference(useRef로 생성한)는 값을 저장하는 용도로도 사용될 수 있지만 Reference는 기본적으로 리액트에서 DOM 조작을 위해 사용

  • reference.current 출력: DOM
  • document.getElementById 출력 = DOM
function App() {
// 1. Javascript in <head>
const reference = useRef(null)
console.log(reference.current) //null
console.log(reference.current?.id) //undefined
console.log(document.getElementById('for-reference')) //null
console.log(document.getElementById('for-reference')?.id) //undefined
useEffect(() => {
// 3. Javascript in end of <body>
console.log(reference.current) //<input id='for-reference' type='number'>
console.log(reference.current?.id) //for-reference
console.log(document.getElementById('for-reference')) //<input id='for-reference' type='number'>
console.log(document.getElementById('for-reference')?.id) //for-reference
}, [])
// 2. HTML (DOM) < 이때 reference 에 DOM 할당
return (
<>
<input id='for-reference' ref={reference} type='number' />
</>
)
}

 

왜 첫번째 4개 로그는 null 혹은 undefined가 찍히고, 그 다음 4개 로그는 제대로 찍히는가?
1. 가장 먼저 useRef 통한 reference 생성(null) -> typescript: useRef 통한 reference생성시 타입니다  null로 초기화 해야함
2. 그 다음 HTML (DOM)모두 파싱 및 로드 <- 이때 reference에 DOM할당
3. 마지막으로 reference 로 DOM조작 가능(reference에 DOM이 연결되어 있으니)

 

Ref는 언제 활용하는가

✅ 1. 리렌더링 감소

많은 값을 가진 큰 객체를 다양한 컴포넌트에서 업데이트하는 경우 리렌더가 사정없이 발생

  • 값 하나만 바뀌더라도 그걸 사용해서 컴포넌트 전체에서 리렌더가 발생
  • 이를 방지하기 위해, 입력 폼으로 어차피 값을 표기되니 실시간 갱신을 하지 말자
  • 제출 버튼을 클릭했을 때만 Ref에 있는 값을 사용

 

실습

실습 코드를 통해 이해해 보자

 

[useState만 사용했을 때]

import { useState } from 'react'
import '@/App.css'
const FEE_ADULT = 20000
const FEE_NON_ADULT = 10000
function App() {
const [age, setAge] = useState(0)
const [valid, setValid] = useState(false)
const [entrance, setEntrance] = useState(FEE_NON_ADULT)
console.log('-rerendering')
return (
<>
<input
type='number'
value={age}
onChange={(event) => {
const changed = Number(event.currentTarget.value)
setAge(changed)
setValid(changed >= 19)
// 성년이 되어도 15000원이 되지 않습니다. 여러분들이 각자 한번 풀어보세요.
setEntrance(changed >= 19 ? FEE_ADULT : FEE_NON_ADULT)
}}
/>
{valid ? (
<div>성년입니다.</div>
) : (
<div style={{ color: 'red' }}>미성년입니다.</div>
)}
<div>{`${entrance}원`}</div>
</>
)
}
export default App

input의 값이 바뀔 때마다 리렌더링이 된다. 코드 복붙한 후 콘솔을 확인해보자

 

[useRef를 사용했을 때]

import { useRef, useState } from 'react'
import '@/App.css'
function App() {
const [valid, setValid] = useState(false)
const ageReference = useRef()
console.log('- rerendered')
return (
<>
<input
ref={ageReference} //ref 돔에 연결
type='number'
onChange={(e) => setValid(Number(e.currentTarget.value) >= 19)}
/>
{valid ? <div>성년입니다</div> : <div style={{ color: 'red' }}>미성년입니다</div>}
</>
)
}
export default App

 

e.currentTarget.value 가 19 이상일 때만 리렌더링 발생하고 input 태그에 값이 바뀔 때마다 리렌더링 되지 않는다.

 

✅ 2. 순수 HTML 요소 간접 조작

<input/> 이런 것들은 React componet가 아니라 내부 상태가 존재하지 않는 순수 HTML요소이다.

DOM 객체(reference)를 통해 간접적으로 조작(리렌더링 없이, 가장 이상적인 용례)

  • DOM 객체(reference) = ref.current 프로퍼티

 

기존에는 순수 HTML 요소 (DOM) 선택을 위해 아래 방법 사용

  1. 일반 JS에서는 document.findElementById()같은걸 사용 (DOM 객체 반환)
  2. jQuery에서는 $ 사용 (jQuery 객체 중 첫번째 요소를 불러야 -> DOM 객체 접근 / 조작 가능)

 

실습

1. useRef 통해 원하는 DOM(HTML 요소)과 연결

  • useEffect 통해 DOM 연결된 이후 시점에서의 referece 조회
import '@/App.css'
import { useEffect, useRef } from 'react'
function App() {
const reference = useRef(null)
console.log('렌더링 전 : ')
console.log(reference.current) //null
console.log(reference.current?.innerText) //undefined
useEffect(() => {
console.log('렌더링 후 : ')
console.log(reference.current) //<div>apple</div>
console.log(reference.current.innerText) //apple
}, [])
return (
<>
<div ref={reference}>apple</div>
</>
)
}
export default App

 

2. useRef 통해 리렌더 없이 DOM(HTML 요소) 조작

  • .current.style.color / .current.className 과 같은 속성들 리렌더 없이 조작 가능
  • useRef 는 값 자체 저장으로도 사용 useRef("mango") / .current = "orange"
import '@/App.css'
import { useRef } from 'react'
function App() {
const reference = useRef(null)
console.log('- rerendered')
return (
<>
<div ref={reference}>apple</div>
<button onClick={(e) => (reference.current.style.color = 'red')}>변경</button>
</>
)
}
export default App

 

 

3. useRef통한 <video> 태그 DOM(HTML 요소) 영상 소스 변경

import '@/App.css'
import { useRef } from 'react'
function App() {
const reference = useRef(null)
const sources = [
'https://vjs.zencdn.net/v/oceans.mp4',
'https://lamberta.github.io/html5-animation/examples/ch04/assets/movieclip.mp4',
]
console.log('- rerendered')
return (
<>
<video ref={reference} autoPlay controls width={500} />
<div>
<button onClick={() => (reference.current.src = sources[0])}>전환 1</button>
<button onClick={() => (reference.current.src = sources[1])}>전환 2</button>
</div>
</>
)
}
export default App

리액트에서 이런 ref를 통해 HTML에 직접 접근해서 무엇을 할 수 있나?

 

1. 특정 요소에 포커스

import { useRef } from 'react'
function Field() {
const inputRef = useRef();
function handleFocus() {
inputRef.current.focus();
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={handleFocus}>입력란 포커스</button>
</>
)
}
​
특정 요소에 Utteran

 

2. 특정 요소에 Utterances 를 붙일 때

const Comments = () => {
const commentRef = useRef(null);
useEffect(() => {
// script element 생성
const utterances = document.createElement('script');
// attribute를 전체를 객체로 만들기
const utterancesConfig = {
src: 'https://utteranc.es/client.js',
repo: 'user/repo',
theme: '선택한 테마',
'issue-term': '포스트 페이지 매핑 방법',
async: true,
crossorigin: 'anonymous',
};
// 객체 전체를 setAttribute로 붙이기
Object.entries(utterancesConfig).forEach(([key, value]) => {
utterances.setAttribute(key, value);
});
// 만든 script를 ref 항목에 appendChild로 붙이기
commentRef.current.appendChild(utterances);
}, []);
return <div ref={commentRef} />;
};
export default Comments;

 

3.  특정 요소에 Observer붙일 때 (스크롤에 따라 상단에 제목을 표기할지 말지..)

import { useRef, useEffect } from 'react'
function SpyExample() {
const spy = useRef()
useEffect(() => {
// 1. Define
const observer = new window.IntersectionObserver(([entry]) => {
if (!entry.intersectionRatio) {
document.getElementById('header-title').classList.add('scrolled-a-bit')
} else {
document.getElementById('header-title').classList.remove('scrolled-a-bit')
}
})
// 2. Attach
observer.observe(spy.current)
// 3. Detach
return () => {
observer.disconnect()
}
}, [])
return (
<div ref={spy} />
)
}

useRef와 setState들이 복잡하게 얽혀있을 때 어떻게 리렌더 영향을 주나?

모든 컴포넌트 그리고 부모-자식에서의 리렌더 기준은 무조건 state(model)가 어디있는지에 의존

  • 부모-자식 간 리렌더는 아래 세가지 중 가장 마지막인 state(model)에 의존한다.
    1. useRef로 생성된 ref가 어디있는지?
    2. useState로 생성된 setState가 어디서 호출됐는지?
    3. useState로 생성된 state가 속한 컴포넌트를 기준으로 (부모로) 모든 자식 컴포넌트 리렌더 -> 이것을 기준으로 리렌더함

 

모든 View의 리렌더의 기준은 model 변경 여부에 의존한다. 이런 특성에 따라 아래와 같은 문제 발생

  1. props drilling: state(model)에 의존하는 view가 State 전달받기 위해 props여행
  2. state의 변경은 state가 속한 부모의 자식 컴포넌트 모두를 리렌더링(나비효과)

기존 MVC 패턴에서의 상태 수직구조의 단점들을 → 새 Flux 패턴에서의 상태 수평구조로 모두 해결

MVC패턴은 수직구조로 변경되는 단점이 있다.

 

forwardRef 써야 ref값을 넘길 수 있다는건 인지했는데, 함수명을 매번 바꿔줘야하나?

yes, 익명 함수 컴포넌트보다 기명 함수 컴포넌트를 forwardRef로 감싸주는 것이 좋음

익명 함수 컴포넌트를 forwardRef로 감싼 경우 디버깅이 어렵다.

 

익명 함수 컴포넌트를 forwardRef로 감싼 경우

const CustomInput = forwardRef(function ({/* value, onChange */}, ref) {
if (true) { throw Error('익명함수에러') }
return <input type='number' ref={ref} /* value={value} onChange={onChange} */ />
})

익명함수에러 Call Stack 최상단에는 의미 모를 _c 만 존재

 

기명 함수 컴포넌트를 forwardRef로 감싼 경우

const CustomInput = forwardRef(function WrappedCustomInput({/* value, onChange */}, ref) {
if (true) { throw Error('익명함수에러') }
return <input type='number' ref={ref} /* value={value} onChange={onChange} */ />
})

기명함수에러 Call Stack 최상단에는 WrappedCustomInput 표기

 

리액트19 버전부터 함수형 컴포넌트에서 Props에 기본으로 ref가 들어감

우리는 실습할 때 forwardRef를 사용하지 않고 파라미터로 ref를 넘겨받아서 사용함

728x90

'ASAC 웹 풀스택' 카테고리의 다른 글

Java 기본 문법 및 JVM 구성(1) - Java동작 원리  (0) 2024.09.25
React 의 특장점, 렌더 라이프사이클 및 Hook(7) - immer(리렌더링 이슈 해결)  (1) 2024.09.22
React 의 특장점, 렌더 라이프사이클 및 Hook(4) - State  (1) 2024.09.22
React 의 특장점, 렌더 라이프사이클 및 Hook(5) - Props  (0) 2024.09.22
React: setState에 대해 더 자세히 알아보자  (0) 2024.09.21
  1. Ref: 외부변수 -> 비제어 컴포넌트
  2. Reference(useRef로 생성한)를 HTML 요소에 연결
  3. Ref는 언제 활용하는가
  4. ✅ 1. 리렌더링 감소
  5. ✅ 2. 순수 HTML 요소 간접 조작
  6. 리액트에서 이런 `ref`를 통해 HTML에 직접 접근해서 무엇을 할 수 있나?
  7. 1. 특정 요소에 포커스
  8. 2. 특정 요소에 Utterances 를 붙일 때
  9. 3.  특정 요소에 Observer붙일 때 (스크롤에 따라 상단에 제목을 표기할지 말지..)
  10. useRef와 setState들이 복잡하게 얽혀있을 때 어떻게 리렌더 영향을 주나?
  11. forwardRef 써야 ref값을 넘길 수 있다는건 인지했는데, 함수명을 매번 바꿔줘야하나?
  12. 익명 함수 컴포넌트를 forwardRef로 감싼 경우
  13. 기명 함수 컴포넌트를 forwardRef로 감싼 경우
  14. 리액트19 버전부터 함수형 컴포넌트에서 Props에 기본으로 ref가 들어감
'ASAC 웹 풀스택' 카테고리의 다른 글
  • Java 기본 문법 및 JVM 구성(1) - Java동작 원리
  • React 의 특장점, 렌더 라이프사이클 및 Hook(7) - immer(리렌더링 이슈 해결)
  • React 의 특장점, 렌더 라이프사이클 및 Hook(4) - State
  • React 의 특장점, 렌더 라이프사이클 및 Hook(5) - Props
hapBday
hapBday
hapBday
개발자로 성장하기 위한 기록들
hapBday
전체
오늘
어제
  • 분류 전체보기 (203)
    • CS (12)
      • 컴퓨터네트워크 (11)
      • 운영체제 (0)
      • 분산 시스템 (0)
      • 데이터베이스 (1)
    • Spring (47)
      • Spring 핵심 원리 (13)
      • Spring MVC (15)
      • Spring DB (12)
      • Spring Security (6)
    • JPA (14)
    • 알고리즘 (30)
      • 프로그래머스 (6)
      • 백준 (20)
    • Design Pattern (0)
    • 언어 (5)
      • JAVA (5)
    • ASAC 웹 풀스택 (38)
      • Spring Boot (21)
      • React (0)
      • DevOps (8)
    • 트러블슈팅 (15)
    • DevOps (5)
      • Docker (5)
    • ETC (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록
  • github

공지사항

인기 글

태그

  • 3-layerd 아키텍쳐 패턴
  • 구현
  • currency control
  • spring security
  • aws lambda
  • 백준
  • 프로그래머스
  • 인프런
  • JPA
  • cookie
  • s-lock
  • MVC
  • Java
  • 오블완
  • 티스토리챌린지
  • Session
  • spring boot
  • docker
  • 트랜잭션
  • 김영한
  • CORS
  • docker workflow
  • docker best practices
  • Spring
  • jwt
  • x-lock
  • CSRF
  • basicerrorcontroller
  • multi-stage
  • S3

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.3.0
hapBday
React 의 특장점, 렌더 라이프사이클 및 Hook(6) - Ref
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.