Home React의 Ref
Post
Cancel

React의 Ref

Ref는 DOM 노드나 리액트 엘리먼트에 접근하는 방법을 제공한다. HTML 요소에 포커스를 주거나 애니메이션 수행을 위해 사용한다. Ref 전달하기는 컴포넌트가 자식 컴포넌트의 ref를 자신의 ref로서 외부에 노출시키게 합니다.

useRef와 forwardRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//📄 Login.js
const Login = () => {
  const emailRef = useRef();
  const passwordRef = useRef();

  return (
    <>
      <Input type="email" ref={emailRef} />
      <Input type="password" ref={passwordRef} />
    </>
  );

  //📄 Input.js
  const Input = React.forwardRef((props, ref) => {
    return <input type={props.type} ref={ref}>
  });
};

부모 컴포넌트인 Login 컴포넌트에서 선언한 ref를 자식 컴포넌트인 Input 컴포넌트로 각각 전달한다. Input 컴포넌트에서 React.forwardRef로 컴포넌트를 생성하였고 React.forwardRef는 props와 ref 파라미터를 받아 React 노드를 반환하는 렌더링 함수를 받는다. 이런 방법으로 Input을 사용하는 컴포넌트들은 input DOM 노드에 대한 참조를 가져올 수 있고, 필요한 경우 DOM input을 직접 사용하는 것처럼 접근할 수 있다.

⚠️ 두 번째 ref 인자는 React.forwardRef와 같이 호출된 컴포넌트를 정의했을 때에만 생성됩니다. 일반 함수나 클래스 컴포넌트는 ref 인자를 받지도 않고 props에서 사용할 수도 없습니다.

useImperativeHandle

Login에서 Input의 ref에 접근하기 위해서는 useImperativeHandle을 사용한다. 위 Login 컴포넌트에 버튼 2개(focusEmail, focusPassword)와 각 버튼을 클릭하면 이메일 또는 비밀번호 Input에 focus를 주는 함수(loginInputFocusHandler, passwordInputFocusHandler)를 추가하였다.

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
29
30
31
32
33
34
35
36
37
//📄 Login.js
const Login = () => {
  const emailRef = useRef();
  const passwordRef = useRef();

  const loginInputFocusHandler = () => {
    emailRef.current.focus();
  }

  const passwordInputFocusHandler = () => {
    passwordRef.current.focus();
  }

  return (
    <>
      <Input type="email" ref={emailRef} />
      <Input type="password" ref={passwordRef} />
      <button id="focusEmail" onClick={loginInputFocusHandler}>Email Focus</button>
      <button id="focusPassword" onClick={passwordInputFocusHandler}>Password Focus</button>
    </>
  );

  //📄 Input.js
  const Input = React.forwardRef((props, ref) => {
    const inputRef = useRef();

    const activate = () => {
        inputRef.current.focus();
    };

    useImperativeHandle(ref, () => {
        return { focus: activate };
    });

    return <input type={props.type} ref={inputRef}>
  });
};

이메일 Input의 포커스를 위한 버튼을 클릭하면 emailRef.current.focus();가 실행되고 이 때 focus()는 Input에서 useImperativeHandle 내에서 반환된 객체의 focus라는 요소의 함수이다. focus는 activate라는 함수를 담고 있기 때문에 최종적으로 activate()가 실행된다.

즉, focusEmail 버튼을 클릭하면 Input의 activate가 실행되며 input DOM 요소에 포커스가 잡힌다.

다른 프로젝트에서 실제 사용한 forwardRef와 useImperativeHandle 알아보기

Tockler는 애플리케이션의 이용 시간을 추적하는 서비스로 일렉트론과 리액트, 타입스크립트로 개발되었다.

메인 화면 상단에는 기간을 선택하고 그 기간만큼의 이용 시간을 차트로 보여주는 기능이 있다. DataRangeInput에서 기간을 선택하는 input 요소의 값이 변경되면 datepicker의 선택된 날짜 값도 변경하는 내용이다.

여기서 사용하는 datePicker 컴포넌트의 소스를 살펴보면 ref를 어떻게 사용하는지 알 수 있다. (실제 프로그램에서는 input 요소의 값을 임의로 변경할 수 없다. 나중에 소스 분석을 하게 되면 자세히 알아봐야겠다.)

tockler-datepicker

아래 코드는 타입스크립트 문법은 생략하고 해당 내용을 설명하기 위한 코드만을 가져온 것이다. 실제 코드는 아래 깃헙 링크를 참고하면 된다.

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
29
30
31
32
33
34
// 📄 DatePicker.tsx
export const DatePicker = React.forwardRef((props, ref) => {
  /* ... */
  // 👇 DatePicker를 호출한 부모 컴포넌트에서 ref로 접근할 수 있도록 onDateSelect라는 함수를 반환한다.
  useImperativeHandler(ref, () => ({
    onDateSelect: (date) => {
      dp.onDateSelect(date);
    },
  }));
  /* ... */
});

// 📄 DateRangeInput.tsx
export const DateRangeInput = (props) => {
  const datePickerRef = useRef();

  function handleInputChange(date) {
    if (
      datePickerRef &&
      datePickerRef.current &&
      datePickerRef.current.onDataSelect
    ) {
      // 👇 onDateSelect는 DatePicker의 useImperativeHandler에서 노출시킨 함수이다.
      datepickerRef.current.onDateSelect(date);
    }
  }

  return (
    /* ... */
    <Input onChange={handleInputChange} /* ... */ />
    <DatePicker ref={datePickerRef} /* ... */ />
    /* ... */
  )
};

DatePicker는 외부에서 ref를 전달받고 useImperativeHandler에서 외부에서 ref에 접근할 수 있는 onDateSelect라는 함수를 반환한다. 그리고 DateRangeInputDatePicker에 datePickerRef라는 ref를 전달하고 Input의 이벤트 핸들러(handleInputChange)에서 이 ref를 통해서 DatePicker의 dp라는 리액트 컴포넌트의 요소를 호출한다.

This post is licensed under CC BY 4.0 by the author.

📸 2022-07-28

📸 2022-07-29