Ref로 DOM 조작하기
React는 렌더링 결과물과 일치하도록DOM을 자동으로 업데이트하므로, 컴포넌트가 DOM을 직접 조작해야 할 일은 많지 않습니다. 하지만 때로는 React가 관리하는 DOM 요소에 접근해야 할 수도 있습니다. 예를 들어, 노드에 포커스를 맞추거나, 스크롤을 이동시키거나, 크기와 위치를 측정하는 경우가 있습니다. React에는 이러한 작업을 수행할 수 있는 내장된 방법이 없으므로 DOM 노드에 대한ref가 필요합니다.
배울 내용
- React가 관리하는 DOM 노드에 접근하는 방법:
ref속성 사용 - JSX의
ref속성과useRefHook의 관계 - 다른 컴포넌트의 DOM 노드에 접근하는 방법
- React가 관리하는 DOM을 수정해도 안전한 경우
노드에 대한 ref 얻기
React가 관리하는 DOM 노드에 접근하려면 먼저useRefHook을 임포트하세요:
그런 다음, 컴포넌트 내부에서 ref를 선언하기 위해 이를 사용하세요:
마지막으로, DOM 노드를 얻고자 하는 JSX 태그의ref 속성에 ref를 전달하세요:
useRef Hook은 current라는 단일 프로퍼티를 가진 객체를 반환합니다. 처음에는myRef.current가 null입니다. React가 이<div>에 대한 DOM 노드를 생성하면, React는 이 노드에 대한 참조를myRef.current에 넣습니다. 그런 다음 이벤트 핸들러에서 이 DOM 노드에 접근하고, 여기에 정의된 내장브라우저 API를 사용할 수 있습니다.
예제: 텍스트 입력 필드에 포커스하기
이 예제에서는 버튼을 클릭하면 입력 필드에 포커스가 설정됩니다:
이를 구현하려면:
- useRef Hook으로
inputRef를useRef선언합니다. - 이를
<input ref={inputRef}>로 전달합니다. 이는 React에게이<input>의 DOM 노드를inputRef.current에 넣으라고 지시합니다. - handleClick 함수에서
handleClickinputRef.current에서 입력 DOM 노드를 읽고inputRef.currentinputReffocus()를 호출합니다.inputRef.current.focus() - onClick을 통해
handleClick이벤트 핸들러를<button>에 전달합니다.onClick
ref는 설정할 때 리렌더링을 트리거하지 않는 state 변수와 같습니다useRef에서 읽어보세요.Ref로 값 참조하기
예제: 요소로 스크롤하기
컴포넌트에 여러 개의 ref를 가질 수 있습니다. 이 예제에는 세 개의 이미지로 이루어진 캐러셀이 있습니다. 각 버튼은 해당 DOM 노드에서 브라우저의scrollIntoView()메서드를 호출하여 이미지를 중앙에 정렬합니다:
다른 컴포넌트의 DOM 노드에 접근하기
주의사항
Ref는 탈출구입니다. 다른컴포넌트의 DOM 노드를 수동으로 조작하면 코드가 취약해질 수 있습니다.
부모 컴포넌트에서 자식 컴포넌트로 ref를 전달할 수 있습니다. 이는다른 prop과 마찬가지로전달됩니다.
위 예시에서 ref는 부모 컴포넌트인MyForm에서 생성되어 자식 컴포넌트인MyInput으로 전달됩니다.MyInput은 ref를 <input>으로 전달합니다.<input>은 내장 컴포넌트이므로 React는 ref의.current 속성을 <input>DOM 요소로 설정합니다.
이제inputRef는 MyForm에서 생성되어<input>DOM 요소를 가리키게 되었습니다. 이 요소는MyInput에 의해 반환됩니다.MyForm에서 생성된 클릭 핸들러는inputRef에 접근하여focus()를 호출하고 <input>에 포커스를 설정할 수 있습니다.
React가 ref를 부착하는 시점
React에서 모든 업데이트는두 단계로 나뉩니다:
- 렌더링단계에서 React는 컴포넌트를 호출하여 화면에 표시할 내용을 파악합니다.
- 커밋단계에서 React는 DOM에 변경 사항을 적용합니다.
일반적으로 렌더링 중에는 ref에 접근하는 것을원하지 않습니다. 이는 DOM 노드를 보유하는 ref에도 적용됩니다. 첫 번째 렌더링 중에는 DOM 노드가 아직 생성되지 않았으므로ref.current는 null이 됩니다. 그리고 업데이트 렌더링 중에는 DOM 노드가 아직 업데이트되지 않았습니다. 따라서 이를 읽기에는 너무 이른 시점입니다.
React는 커밋 단계에서ref.current를 설정합니다. DOM을 업데이트하기 전에 React는 영향을 받는ref.current 값을 null로 설정합니다. DOM을 업데이트한 후에는 React가 즉시 해당 DOM 노드로 설정합니다.
일반적으로 이벤트 핸들러에서 ref에 접근하게 됩니다.ref로 무언가를 하고 싶지만, 이를 수행할 특정 이벤트가 없다면 Effect가 필요할 수 있습니다. Effect에 대해서는 다음 페이지에서 논의하겠습니다.
Ref를 사용한 DOM 조작의 모범 사례
Ref는 탈출구입니다. "React 외부로 나가야 할 때"에만 사용해야 합니다. 일반적인 예로는 포커스 관리, 스크롤 위치 관리, 또는 React가 노출하지 않는 브라우저 API 호출이 있습니다.
포커싱이나 스크롤링과 같은 비파괴적인 작업에만 집중한다면 문제를 만나지 않을 것입니다. 그러나 DOM을 직접수정하려고 시도한다면, React가 만드는 변경 사항과 충돌할 위험이 있습니다.
이 문제를 설명하기 위해, 이 예제에는 환영 메시지와 두 개의 버튼이 포함되어 있습니다. 첫 번째 버튼은 React에서 일반적으로 하는 것처럼조건부 렌더링과 상태를 사용하여 메시지의 존재를 토글합니다. 두 번째 버튼은remove() DOM API를 사용하여 React의 통제 밖에서 강제로 메시지를 DOM에서 제거합니다.
"Toggle with setState" 버튼을 몇 번 눌러 보세요. 메시지가 사라졌다가 다시 나타날 것입니다. 그런 다음 "Remove from the DOM" 버튼을 누르세요. 이렇게 하면 메시지가 강제로 제거됩니다. 마지막으로 "Toggle with setState" 버튼을 다시 눌러 보세요:
DOM 요소를 수동으로 제거한 후,setState를 사용하여 다시 표시하려고 하면 충돌이 발생합니다. 이는 DOM을 변경했고, React가 올바르게 계속 관리하는 방법을 알지 못하기 때문입니다.
React가 관리하는 DOM 노드를 변경하지 마세요.React가 관리하는 요소를 수정하거나, 자식 요소를 추가하거나 제거하면 위와 같은 불일치한 시각적 결과나 충돌이 발생할 수 있습니다.
그러나 이것이 전혀 할 수 없다는 의미는 아닙니다. 주의가 필요합니다.React가 업데이트할 이유가 없는DOM 부분은 안전하게 수정할 수 있습니다.예를 들어, JSX에서 항상 비어 있는<div>가 있다면, React는 그 자식 목록을 건드릴 이유가 없습니다. 따라서 그곳에 수동으로 요소를 추가하거나 제거하는 것은 안전합니다.
요약
- Ref는 일반적인 개념이지만, 대부분 DOM 요소를 보유하기 위해 사용하게 될 것입니다.
- React에 DOM 노드를
myRef.current에 넣도록 지시하려면<div ref={myRef}>를 전달하면 됩니다. - 일반적으로 ref는 포커싱, 스크롤링 또는 DOM 요소 측정과 같은 비파괴적인 작업에 사용됩니다.
- 컴포넌트는 기본적으로 DOM 노드를 노출하지 않습니다.
refprop을 사용하여 DOM 노드 노출을 선택할 수
Try out some challenges
Challenge 1 of 4:Play and pause the video #
In this example, the button toggles a state variable to switch between a playing and a paused state. However, in order to actually play or pause the video, toggling state is not enough. You also need to call play() and pause() on the DOM element for the <video>. Add a ref to it, and make the button work.
For an extra challenge, keep the “Play” button in sync with whether the video is playing even if the user right-clicks the video and plays it using the built-in browser media controls. You might want to listen to onPlay and onPause on the video to do that.
