본문 바로가기

리액트 공식 문서 읽기

24화: Manipulating the DOM with Refs

이 글은 단순히 리액트 공식 문서를 읽고 베꼈을 뿐이다!! 학습 목적이라면 공식 문서를 보는 걸 추천한다.

 

참새. Unsplash 에 Patrice Bouchard 님이 올림

공식 문서

https://react.dev/learn/manipulating-the-dom-with-refs

 

Manipulating the DOM with Refs – React

The library for web and native user interfaces

react.dev

노드의 ref를 얻기

  1. import { useRef } from 'react';
  2. const myRef = useRef(null);
  3. <div ref={myRef}>
  • 이벤트 핸들러 등에서 myRef.current를 이용해 DOM node에 접근하여 scrollIntoView(), focus() 같은 브라우저 내장 api 사용 가능
  • 배열의 map을 돌아서 나온 JSX에 ref를 부여하는 등의 작업이 필요할 때
    • querySelectorAll 같은 메서드로 한번에 찾아서 넣기: 불안정함. DOM이 바뀌면 망가질 수 있음
    • ref callback 만들기: DOM에 ref={myRef}가 아니라 ref={(node) => setNodeToMap(node, id)} 이런 식으로 하면 DOM ref를 설정할 시기에 해당 함수를 이용함

다른 컴포넌트의 DOM node에 접근하기

  • ref를 DOM에 먹이는 건 브라우저의 built-in element들만 가능함
  • 내가 만든 컴포넌트에 바로 ref를 먹이면 null이 나옴
  • 기본적으로 리액트는 컴포넌트에서 다른 컴포넌트의 DOM node 접근을 불허함
  • ref는 비상탈출구: 아꼈다가 정말정말 필요한 곳에 써야 함
  • 본인의 DOM node에 대한 외부의 접근을 허용하려면 명시적으로 표시해야 함: forwardRef 사용이런 식으로 '나 ref 받아요' 라고 광고해야 한다.
  • const MyInput = forwardRef((props, ref) => { return <input {...props} ref={ref} />; });

useImperativeHandle로 DOM 조작의 일부만을 허용하기

forwardRef로 본인의 DOM node를 공개하면 밖에서 접근이 가능하다. 이 말인즉슨 단순히 포커싱이나 스크롤만이 아니라 입력창 내부의 값이나 css도 조작할 수 있다는 뜻이다!
리액트에서 제공하는 useImperativeHandle을 이용하면 공개된 ref의 기능들을 제한할 수 있다.

const MyInput = forwardRef((props, ref) => {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() {
      realInputRef.current.focus();
    },
  }));
  return <input {...props} ref={realInputRef} />;
});

이러면 외부에서는 focus()밖에 사용할 수 없게 된다.

리액트가 ref에 값을 할당하는 순간

  • 리액트에서 모든 업데이트는 두 단계로 나눌 수 있음
    1. render: 컴포넌트를 호출해 JSX 트리를 받아옴
    2. commit: UI 트리의 변화를 DOM에 반영함
  • 일반적으로 ref는 렌더링 과정에서 사용하지 않음
    • 따라서 맨 처음 렌더링에는 당연히 DOM이 아직 없으므로 ref.current가 null
    • 리렌더링에서는 아직 DOM node가 업데이트되지 않았으므로 ref.current를 읽기에는 너무 빠른 타이밍
  • 리액트는 ref.current값을 commit 단계에서 설정함: 업데이트 전에는 null로 하고 직후에 상응하는 DOM node로 바꿈
  • 일반적으로 ref는 이벤트 핸들러에서 접근하게 됨: 이벤트는 딱히 없는데 ref를 건드리고 싶다면 effect로 가능(다음 장에 자세히 나옴)

flushSync 를 이용해서 동기적으로 DOM 업데이트하기

  • 어떤 배열 상태에 map을 이용해 DOM 목록들을 만들고 거기에 대한 ref를 얻는다고 치자
  • 우리가 해당 배열 상태를 변경하면 (상태 변경은 비동기적이므로) 즉각적으로 ref에 변경된 배열을 이용한 DOM이 담기지 않음
  • 이럴 때 flushSync를 사용하면 flushSync 내부의 콜백 함수 실행이 끝나자마자 DOM을 업데이트하도록 함
  • 따라서 flushSync 이후에 ref를 접근하면 새로 바뀐 DOM을 이용할 수 있음

ref를 이용한 DOM 조작 맛있게 하기

  • ref는 비상탈출구: '리액트 외부의 기능이 필요할 때'만 이용하기
  • 파괴적이지 않은 기능을 이용할 때는 괜찮음: 스크롤, 포커스, ...
  • 하지만 이걸로 DOM을 조작하려고 하면 리액트의 DOM 조작과 충돌 위험성이 있음
  • 리액트가 관리하는 DOM을 조작하지 말기
  • 물론 리액트가 절대 건드리지 않는 DOM은 조작해도 됨