일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 파이썬 딕셔너리
- 파이썬 반복문
- 리액트 훅
- 타입스크립트 리액트
- typeScript
- 파이썬 for
- 리액트 프로젝트
- 타입스크립트 props
- 파이썬 enumerate
- 리액트 공식문서
- React Hooks
- 파이썬 replace
- Next 팀 프로젝트
- 내일배움캠프 프로젝트
- 내일배움캠프
- 내일배움캠프 최종 프로젝트
- tanstack query
- 한글 공부 사이트
- 내배캠 프로젝트
- 파이썬 for in
- 프로그래머스
- JavaScript
- 리액트
- 코딩테스트
- 자바스크립트
- 타입스크립트
- useState
- useEffect
- 파이썬 slice
- REACT
- Today
- Total
sohyeon kim
[React] 리액트 공식문서 정리 4(1) : 탈출구 - useRef, DOM 직접 조작, 리렌더링 방지 본문
💡 리액트 공식문서 정리 : 탈출구 - Ref 로 값 참조하기, Ref 로 DOM 조작하기
일부 컴포넌트는 React 외부의 시스템을 제어하고 동기화해야 할 수 있다.
e.g. 브라우저 API 를 사용해 input 에 초점 맞추기, React 없이 구현된 비디오 플레이어 재생, 원격 서버에 연결해 메시지 수신
이 장에선 React 의 '외부'로 나가 외부 시스템에 연결할 수 있는 탈출구를 배운다.
* 대부분의 애플리케이션 로직과 데이터 흐름은 이러한 기능에 의존해선 안된다.
1. Ref 로 값 참조하기 : 다시 렌더링하지 않고 정보를 '기억'하는 방법
컴포넌트 내 데이터를 유지하고 싶지만, 렌더링은 일으키고 싶지 않다면 ref 를 사용하자.
state 처럼 ref 로 값을 유지할 수 있다. 다만 state 는 리렌더링을 일으키지만, ref 는 그렇지 않다.
✨ ref ✨
- React 가 추적하지 않는 컴포넌트의 비밀 주머니
- React 의 단방향 데이터 흐름에서 '탈출구'가 되는 것!
- ref.current 프로퍼티를 통해 해당 ref 의 현재 값에 접근 가능하다. 이 값은 읽거나 쓰거나 의도적인 변경이 가능
- 렌더링에 영향을 주지 않는 timeout ID, DOM 엘리먼트 및 기타 객체를 저장할 수 있다.
- ref 는 state 처럼 숫자, 문자열, 객체, 함수 등 모든 것을 가리킬 수 있다.
- ref 는 state 와 달리 읽고 수정할 수 있는 current 프로퍼티를 가진 일반 자바스크립트 객체이다.
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1; // 클릭 시 ref.current 가 증가, 하지만 리렌더링되지 않는다.
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
컴포넌트가 리렌더링 되면 모든 로컬 변수(state) 는 초기화된다. 대신 ref 에 저장하면 이 ref 는 렌더 사이에 React 에 보존된다.
🪄 어딘가에(비밀 주머니) 보관한 것
import { useState, useRef } from 'react';
export default function Chat() {
const [text, setText] = useState('');
const [isSending, setIsSending] = useState(false);
const timeoutRef = useRef(null);
function handleSend() {
setIsSending(true);
timeoutRef.current = setTimeout(() => { // timeout ID 를 저장.
alert('Sent!');
setIsSending(false);
}, 3000);
}
function handleUndo() { // send 중 취소하는 핸들러
setIsSending(false);
clearTimeout(timeoutRef.current);
}
return (
<>
<input
disabled={isSending}
value={text}
onChange={e => setText(e.target.value)}
/>
<button
disabled={isSending}
onClick={handleSend}>
{isSending ? 'Sending...' : 'Send'}
</button>
{isSending &&
<button onClick={handleUndo}>
Undo
</button>
}
</>
);
}
ref | state |
useRef(initialValue) 는 { current: initialValue } 을 반환 | useState(initialValue) 은 state 변수의 현재 값과 setter 함수 [value, setValue] 를 반환 |
state를 바꿔도 리렌더 되지 않음 | state를 바꾸면 리렌더 |
Mutable - 렌더링 프로세스 외부에서 current 값 수정 및 업데이트 가능. 리렌더링과 독립적으로 업데이트(최신 값을 유지) | Immutable - state 를 수정하기 위해선 state 설정 함수를 반드시 사용하여 리렌더 대기열에 넣어야 함, 렌더링이 끝날 때까지 이전 값 사용 |
렌더링 중에 current 값을 읽거나 쓰면 안 된다. 렌더링 중 일부 정보가 필요하다면 state 를 대신 사용. ref.current 가 언제 변하는지 React 는 모르기 때문에 렌더링 시 읽어도 컴포넌트의 동작을 예측하기 어렵다. | 언제든지 state 를 읽을 수 있다. 그러나 각 렌더마다 변경되지 않는 자체적인 state의 snapshot 이 있다. |
2. Ref 로 DOM 조작하기 : React 가 관리하는 DOM 엘리먼트에 접근하는 방법
React 는 렌더링 결과물에 맞춰 DOM 변경을 자동으로 처리하기 때문에 자주 DOM 조작을 할 필요는 없다.
하지만 가끔 DOM 요소에 직접 접근해야 할 때가 있다. 이럴 때 ref 가 필요하다.
e.g. 특정 노드에 포커스, 스크롤 위치 이동, 위치와 크기 측정 시
<div ref={myRef}> <!-- 이제 React 는 이 DOM 노드를 myRef.current 에 넣는다. -->
<!-- 이 DOM 노드를 이벤트 핸들러에서 접근하거나 -->
<!-- 노드에 정의된 내장 브라우저 API 를 사용할 수 있다. -->
myRef.current.scrollIntoView();
스크롤 이동
한 컴포넌트에 하나 이상의 ref 를 가질 수 있다. 아래는 이미지 3개가 있는 캐러셀 예시이다.
import { useRef } from 'react';
export default function CatFriends() {
const firstCatRef = useRef(null);
const secondCatRef = useRef(null);
const thirdCatRef = useRef(null);
// 각 버튼은 브라우저 scrollIntoView 메서드를 해당 DOM 노드로 호출해 이미지를 중앙에 배치한다.
function handleScrollToFirstCat() {
firstCatRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}
// ...
return (
<>
<nav>
<button onClick={handleScrollToFirstCat}>
Neo
</button>
// ...
</nav>
<div>
<ul>
<li>
<img
src="https://placecats.com/neo/300/200"
alt="Neo"
ref={firstCatRef}
/>
</li>
// ...
</ul>
</div>
</>
);
}
기본적으로 ref 로 다른 컴포넌트의 DOM 노드에 접근할 수 없다.
다른 컴포넌트의 DOM 노드를 조작하는 것은 오류를 일으킬 수 있다.
대신 DOM 노드를 선택적으로 노출해 ref 를 '전달'하도록 지정할 수 있다.
// SearchInput.js
import { forwardRef } from 'react';
export default forwardRef(
function SearchInput(props, ref) { // 두 번째 인수로 상위의 inputRef 를 받는다.
return (
<input
ref={ref}
placeholder="Looking for something?"
/>
);
}
);
// App
import { useRef } from 'react';
import SearchInput from './SearchInput.js';
export default function Page() {
const inputRef = useRef(null);
return (
<>
<nav>
<button onClick={() => {
inputRef.current.focus();
}}>
Search
</button
</nav>
<SearchInput ref={inputRef} />
</>
);
}
이 패턴은 디자인 시스템에서 버튼, 입력 요소 등 저수준 컴포넌트에서 DOM 노드를 전달하기 위해 흔하게 사용된다.
반면 폼, 목록, 페이지 섹션 등 고수준 컴포넌트에선 의도치 않은 DOM 구조 의존성 문제를 피하고자 일반적으로 DOM 노드를 노출하지 않는다.
React 가 ref 를 부여할 때
React 의 모든 갱신은 두 단계로 나눌 수 있다.
- 렌더링 단계 : 화면에 무엇을 그려야 하는지 알아내도록 컴포넌트를 호출
- 커밋 단계 : 변경사항을 DOM 에 적용
첫 렌더링에서 DOM 노드는 아직 생성되지 않아 ref.current 는 아직 null 인 상태이다.
갱신에 의한 렌더링에서 DOM 노드는 아직 업데이트 되지 않은 상태로, 두 상황 모두 ref 를 읽기에 너무 이른 상황이다.
React 는 ref.current 를 커밋 단계에서 설정한다. DOM 을 변경하기 전 React 는 관련된 ref.current 값을 미리 null 로 설정하고,
DOM 을 변경한 후 즉시 대응하는 DOM 노드로 다시 설정한다.
대부분 ref 접근은 이벤트 핸들러 내에서 일어난다. ref 를 사용해 뭔갈 하고 싶지만, 실행할 특정 이벤트가 없을 때 Effect 가 필요할 수도 있다.
➕ flushSync : 상태 업데이트를 동기적으로 처리해 강제로 즉시 렌더링을 실행
React 의 기본 상태 업데이트는 비동기적으로 처리되며, 최적화를 위해 배치 batch 로 묶어서 처리된다. (count 생각)
하지만 특정 상황에서 동기적으로 상태를 업데이트하고 즉시 렌더링 결과를 얻어야할 때가 있다. 이 때 flushSync 를 사용한다.
- DOM 을 동기적으로 조작해야 할 때(DOM 조작과의 동기화가 필요할 때) 사용
- 즉각적인 UI 업데이트(상태 업데이트)가 반영되어야 할 때 사용
- React 의 기본 비동개 배치 처리 방식을 무시하므로 성능 최적화에 부정적인 영향을 줄 수 있으므로 꼭 필요한 경우에 사용
- 비동기 함수 내에서 사용 불가
import { flushSync } from 'react-dom';
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
flushSync(() => { // 상태 업데이트를 동기적으로 처리
setCount(count + 1);
});
console.log('Updated count:', count); // 동기적으로 업데이트된 count 값을 즉시 확인 가능
}
return (
<>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</>
);
}
❗️DOM 을 직접 수정할 땐 다른 변경 사항과의 충돌을 주의해야 한다. React 가 관리하는 DOM 노드를 직접 바꾸려 하지 말 것.
상태에 따라 문구를 토글할 수 있는데, remove() 를 사용해 노드를 강제로 삭제하면 이후 토글은 에러가 발생한다.
다만 React 가 관리하지 않는(업데이트하지 않는) 빈 노드 같은 경우 추가하거나 삭제할 수 있다.
import {useState, useRef} from 'react';
export default function Counter() {
const [show, setShow] = useState(true);
const ref = useRef(null);
return (
<div>
<button
onClick={() => {
setShow(!show);
}}>
Toggle with setState
</button>
<button
onClick={() => {
ref.current.remove();
}}>
Remove from the DOM
</button>
{show && <p ref={ref}>Hello world</p>}
</div>
);
}
🔗 https://ko.react.dev/learn/escape-hatches