프론트 개발 블로그

Ref 와 DOM 본문

React

Ref 와 DOM

maybe.b50 2020. 12. 17. 23:56

Ref 란?

React 에서 컴포넌트에 접근할 수 있는 특수한 어트리뷰트를 말합니다. 

React.createRef() 함수 / 콜백 함수 / 문자열(레거시 API) 로 생성할 수 있습니다. 

ref 어트리뷰트가 콜백 함수인 경우, 함수는 DOM 엘리먼트나 Class 인스턴스를 인자로 받습니다. 

 

Ref 사용 

함수형 컴포넌트를 사용할 땐 : useRef() Hook API 

클래스형 컴포넌트를 사용할 땐 : React.createRef() 메서드

 

React 에서 DOM 을 직접 선택 해야 할 떄, Ref 를 사용합니다. 

  • 특정 엘리먼트의 크기나 위치를 선택 해야 할 때 
  • 포커스 설정을 해야 할 때 
  • 스크롤 바의 위치나 설정 등
  • 미디어의 재생을 관리 할 때 (VideoJS 등 HTML5 비디오 관련)
  • 서드 파티 DOM 라이브러리를 React 와 같이 사용할 때 (D3, ChartJS 등 특정 DOM)
선언적으로 해결할 수 있는 문제에서는 ref 사용을 피해야 합니다.
예를 들어, Diglog 컴포넌트에서 open() 과 close() 메서드 대신에 isOpen이라는 prop 를 사용해서 제어합니다. 

 

Ref 를 남용하지 마세요.

ref 는 애플리케이션에서 '어떤 일이 일어나게' 할 때 사용될 수도 있는데,

이런 경우 어느 컴포넌트 계층에서 상태를 소유해야 하는 지 생각해보아야 합니다. 

대게 상태를 소유해야 하는 적절한 장소는 더 높은 계층이란는 결론이 나고, 

상태를 상위 계층으로 올리는 'State 끌어올리기' 방법으로 ref 대신에 사용할 수 있습니다. 

 

 

Ref 생성하기 

ref 는 React.createRef() 를 통해 생성합니다. 

ref 속성을 통해 React 엘리먼트에 부착합니다. 

보통 컴포넌트의 인스턴스가 생성 될 때(constructor) ref 를 프로퍼티로 추가하고,

컴포넌트의 인스턴스의 어느 곳에서라도 ref 에 접근할 수 있게 해야 합니다. 

* 함수 컴포넌트는 인스턴스가 없기 때문에 ref 어트리뷰트를 사용할 수 없습니다 (대신 useRef() Hook 사용하면 됨)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from 'react';
 
class RefStudy extends Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();  // ref 생성하기 
    }
 
    render() {
        return(
            <div ref={this.myRef} />  // ref 어트리뷰티 속성을 통해 React 엘리먼트에 부착
        )
    }
}
 
export default RefStudy;
 

 

 

Ref 에 접근하기 

render 메서드 안에서 ref 가 엘리먼트에 전달 되었을 때,

그 노드의 향한 참조는 ref 의 current 어트리뷰트에 담기게 됩니다. 

1
const node = this.myRef.current;

ref 의 값은 노드 유형에 따라 다릅니다. 

 

1. HTML 엘리먼트에서 쓰인 ref 어트리뷰트

: ref는 자신을 전달 받은 DOM 엘리먼트를 current 프로퍼티의 값으로 받습니다. 

2. 커스텀 class 컴포넌트에서 쓰인 ref 어트리뷰트 

: ref 객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티 값으로 받습니다. 

3. 함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없습니다. 

 

 

DOM 엘리먼트에 Ref 사용하기 

DOM 노드에 대한 참조를 저장하기 위한 ref 사용 예시 :

1. constructor 에서 textInput DOM 엘리먼트를 저장하기 위한 ref 를 생성합니다. 

2. 리액트에게 input 엘리먼트를 우리가 생성자에서 생성한 ref 와 연결하고 싶다고 이야기 합니다.

3. DOM API 를 사용하여 명시적으로 input 엘리먼트에 포커스 합니다. 

     DOM 노드로 접근하기 위해 'current' 프로퍼티에 접근하고 있습니다. 

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
import React from 'react';
 
class CustomTextInput extends React.Component {
    constructor(props) {
        super(props);
        // 1. textInput DOM 엘리먼트를 저장하기 위한 ref 를 생성합니다.
        this.textInput = React.createRef();
        this.focusTextInput = this.focusTextInput.bind(this);
    }
 
    focusTextInput() {
                // 3. DOM API 를 사용하여 명시적으로 input 엘리먼트에 포커스합니다.
                // DOM 노드로 접근하기 위해 'current' 프로퍼티에 접근하고 있습니다.
        this.textInput.current.focus();
    }
    
    render() {
        return(
            <>
                {/* 2. React 에게 input 엘리먼트를 우리가 생성자에서 생성한 
                      ref 와 연결하고 싶다고 이야기 합니다 */
                <input 
                    type="text"
                    ref={this.textInput} 
                />
                <input 
                    type="button"
                    value="Focus the text input"
                    onClick={this.focusTextInput}
                />
            </>
        );
    }
}
 
export default CustomTextInput;

마운트 될 때만 React는 current 프로퍼티에 DOM 엘리먼트에 대입하고, 

마운트가 해제 될 때 current 프로퍼티를 다시 null 로 돌려놓습니다. 

ref를 수정하는 작업은 componentDidMount 또는 componentDidUpdate 생명주기 메서드가

호출 되기 전에 이루어집니다. 

 

1
2
3
constructor {current: null}
render {current: null}
focuseTextInput {current: input}    // 마운트 시에만 DOM 엘리먼트에 대입함

 

 

클래스 컴포넌트에 Ref 사용하기 

CustomTextInput 컴포넌트의 인스턴스가 마운트 된 이후에 input 에 즉시 포커스 되는 것을 흉내내기 위한 코드입니다. 

ref 를 이용하여 CustomTextInput 컴포넌트의 인스턴스에 접근하고,

직접 focusTextInput 메서드를 호출 할 수 있습니다. 

* 해당 코드는 CustomTextInput 컴포넌트가 클래스형 컴포넌트 일 때만 작동합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component, createRef } from 'react';
import CustomTextInput from './CustomTextInput';
 
class AutoFocusTextInput extends Component {
    constructor(props) {
        super(props);
       // 1. ref 생성 후 
        this.textInput = createRef();  
    }
 
    componentDidMount() {
       //3.CustomTextInput 컴포넌트의 인스턴스에 접근하고 직접 focusTextInput() 메서드 호출
        this.textInput.current.focusTextInput(); 
    }
 
    render() {
        return(
           {/* 2. ref 를 연결 */}
            <CustomTextInput ref={this.textInput} />
        );
    }
}
 
export default AutoFocusTextInput;
 

 

 

Ref 와 함수 컴포넌트 

함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없습니다. 

함수 컴포넌트에 ref 를 사용할 수 있도록 하려면,

forwardRef (높은 확률로 useImperativeHandle도 함께) 사용하거나 클래스형 컴포넌트로 사용해야 합니다. 

 

다만, DOM 엘리먼트나 클래스 컴포넌트 인스턴스에 접근하기 위해

ref 어트리뷰트를 함수 컴포넌트에서 사용하는 것은 됩니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function CustomTextInput(props) {
    const textInput = useRef(null);
    
    function handleClick() {
        textInput.current.focus();
    }
 
    return(
        <>
            <input 
                type="text"
                ref={textInput}
            />
            <input
                type="button"
                value="focus the text input"
                onClick={handleClick}
            />
        </>
    );
}

 

 

부모 컴포넌트에게 DOM ref 를 공개하기

보기 드문 경우지만 부모 컴포넌트에서 자식 컴포넌트의 DOM 노드에 접근하고자 할 때

 

1. 자식의 DOM 노드에 포커스 

2. 크기 또는 계산 하는 작업 등 

 

A. ref 전달하기 (React.forwardRef API) 를 통해서 사용할 수 있습니다. 

* 자식 컴포넌트가 함수 컴포넌트인 경우 동작하지 않습니다. 

 

React 16.3 이후 버전 : forwardRef API 사용가능

React 16.2 이전 버전 : forwardRef 보다 유연한 방법을 원한다면 

https://gist.github.com/gaearon/1a018a023347fe1c2476073330cc5509

더보기

React 16.2 이전 버전에서 forwardRef 보다 유연한 방법을 원한다면?

React 16.3 이상으로 마이그레이션 할 수 없을 때 다음 접근 방식을 대신 사용할 수 있습니다.

자식에게 특별한 props 를 노출시킵니다. 이 props 는 ref 이외의 이름으로 지정할 수 있습니다.

그런 다음 자식 구성 요소는 prop을 ref 속성으로 DOM 노드에 전달할 수 있습니다.

이렇게 하면 부모가 중간에 있는 컴포넌트를 통해 자식의 DOM 노드에 참조를 전달할 수 있습니다.

클래스 컴포넌트, 함수형 컴포넌트에서 해당 기능은 모두 작동됩니다. 

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.inputElement = React.createRef();
  }
  render() {
    return (
      <CustomTextInput inputRef={this.inputElement} />
    );
  }
}
function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

function Parent(props) {
  return (
    <div>
      My input: <CustomTextInput inputRef={props.inputRef} />
    </div>
  );
}

class Grandparent extends React.Component {
  constructor(props) {
    super(props);
    this.inputElement = React.createRef();
  }
  render() {
    return (
      <Parent inputRef={this.inputElement} />
    );
  }
}
가능하다면 DOM 노드를 외부에 공개하는 일은 피해야 합니다.

 

콜백 Ref 

ref 가 설정되고 해제되는 상황을 세세하게 다룰 수 있는 콜백  ref 가 부르는 방법을 제공합니다.

콜백 ref 를 사용할 때에는 ref 어트리뷰트에 React.creatRef() 를 통해 생성된 ref 를 전달하는 대신, 함수를 전달합니다.

다른 곳에 저장되고 접근될 수 있는 React 컴포넌트의 인스턴스나 DOM 엘리먼트를 인자로서 받습니다.

 

1. 인스턴스가 마운트 될 때 React 는 ref 콜백을 DOM 엘리먼트와 함께 호출합니다.

2. 그리고 컴포넌트의 인스턴스의 마운트가 해제될 때 ref 콜백을 null 과 함께 호출합니다. 

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
38
39
40
import React, { Component } from 'react';
 
class CallbackRef extends Component {
    constructor(props) {
        super(props);
        this.textInput = null;
        this.setTextInputRef = (element) => {
            console.log(element);   // <input type="text" />
            this.textInput = element;
        }
 
        this.focusTextInput = () => {
            if(this.textInput) {
                this.textInput.focus();
            }
        }
    }
 
    componentDidMount() {
        this.focusTextInput();
    }
 
    render() {
        return(
            <>  
                <input 
                    type="text"
                    ref={this.setTextInputRef} 
                />
                <input 
                    type="button"
                    value="Focus the text input"
                    onClick={this.focusTextInput}
                />
            </>
        );
    }
}
 
export default CallbackRef;
 

 

콜백 ref 또한 React.createRef() 로 생성했던 객체 ref 와 같이 다른 컴포넌트에 전달할 수 있습니다. 

1. Parent 는 자신의 콜백 ref 를 props로서 CustomTextInput 에게 전달합니다.

2. CustomTextInput 은 전달받은 함수를 input 에게 ref 어트리뷰트로 전달합니다.

3. 결과적으로 Parent 에 있는 this.inputElement 는 CustomTextInput 의 <input> 엘리먼트에 대응하는 DOM 노드가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Parent extends React.Components {
    render() {
        return(
            <CustomTextInput inputRef={el => this.inputElement = el} />
        );
    }
}
 
function CustomTextInput(props) {
    return(
        <>
            <input ref={props.inputRef} />
    </>
    );
}

 

 

콜백 ref 에 대한 주의사항

ref 콜백이 인라인 함수로 선언되었다면 ?

ref 콜백은 업데이트 과정 중 처음에는 null 로, 그 다음에는 DOM 엘리먼트로 총 두 번 호출 됩니다.

이러한 과정은 매 렌더링 될 때마다 ref 콜백의 새 인스턴스가 생성되고, React 가 이전에 사용된 ref 를 제거하고 새 ref 를 설정해야 합니다. 

해결 방법 : ref 콜백을 클래스에 바인딩된 메서드로 선언함으로써 해결할 수 있습니다.

 

* 인라인 함수란?

   인라인 함수는 React 가 렌더링 될 때 정의되는 함수를 말합니다. 

 

 

레거시 API : 문자열 ref 

ref 에 접근하기 위해 this.refs.textInput 과 같이 문자열 ref 로 사용하고 있었다면,

콜백 refcreateRef() API 를 대신 사용하는 것을 권해드립니다. 

* 차후 배포에서 삭제 될 것으로 예상됨.

 

 

 


 

Ref 정리 

1. ref 는 컴포넌트에서 접근할 수 있는 어트리뷰트를 말한다.

2. 리액트에서 DOM 엘리먼트에 접근해야 할 때 사용하면 좋다.

3. 클래스형 컴포넌트에서는 React.createRef() 메서드를, 함수형 컴포넌트에서는 useRef() hook 을 사용할 수 있다.

4. ref 는 createRef() 함수, 콜백 함수, 문자열로 사용할 수 있다.

 

 

궁금증?

1. 자식 컴포넌트가 함수 컴포넌트 일 때 왜 동작하지 않는가? 인스턴스가 존재하지 않아서? 

하지만 예시에서 DOM 엘리먼트나 클래스 컴포넌트의 인스턴스에 접근 하려고 useRef() 를 통해서 접근하는건 되었다.

함수 컴포넌트 일 때는 ref 가 전달이 되지 않는건가? 그런데 React 16.2 이전 버전에서 forwardRef 메서드 보다 유연한 방법으로 함수형 컴포넌트에서 '자식에게 특별한 props 를 노출 시키고, 자식 요소에 props 를 ref 속성으로 전달했다' 

이렇게 사용하는건 괜찮은건가?? 알아보기

 

 

 

반응형

'React' 카테고리의 다른 글

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0.  (0) 2021.01.05
windowing 라이브러리  (0) 2021.01.05
Forwarding Refs (React.forwardRef)  (0) 2020.12.14
Context API  (0) 2020.12.10
[Errer] name can no longer contain capital letters  (0) 2020.10.28