[React] Ref

이호정·2022년 10월 13일
0

React

목록 보기
3/5

네이버 블로그에서 이전, 원글 작성일시 : 2021.07.17.03:40

제로초 님은 늘 컴포넌트를 class, 함수형 두 가지 방식으로 각각 만들어 설명해주신다.

ref를 이용하여 dom에 접근해야할 경우, 두 가지 방식의 사용법이 다르기 때문에
점점 헷갈리기 시작해 정리하고 넘어가려 한다.

ref가 뭘까? html 공식 attribute는 아니고 react에서만 사용하는 것으로 보인다.

이름에서 유추할 수 있듯이 reference 즉, 참조를 뜻하는 것으로 보인다.

아래 클래스 컴포넌트의 render 메소드를 살펴 보자

render() {
        return (
            <React.Fragment>
                <div>{this.state.first}곱하기{this.state.second}?</div>
                <form onSubmit={this.onSubmit}>
                    <input ref={this.onRefInput} type="number" value={this.state.value} onChange={this.onChange}/>
                    <button>입력!</button>
                    </form>
                <div>{this.state.result}</div>
            </React.Fragment>
        );
    } 

주의 깊게 볼것은 input 태그의 ref 이다.
위 예제는 구구단으로, 간단히 O x O = ? 하고 물으면 input 태그에 값을 입력하고 정답인지 아닌지 출력해준다.

여기서 내가 원하는 것은 입력(onsubmit)을 누른후 다시 input 태그로 포커스를 옮기고 싶다.
그냥 자바스크립트는 onsubmit에서 dom으로 input node 찾고 focus()면 끝인데, react에서는 어떻게 하느냐?

위 예시처럼 render 안의 접근하고 싶은 태그(input)의 ref, 참조를 어딘가에 저장해놓는다. 이게 핵심이다.
언제든 꺼내쓸 수 있게 어딘가에 저장해놓는 것이다. 그렇다면 이 저장해놓는 방법에 대해 알아보자.

(사실 위 예시의 onRefInput은 함수이다... )


1. class component

여기서도 두 가지 방법이 있다.

첫번째 방법 : 멤버변수(라고해야하나?)에 참조 저장, 주석만 읽어봐도 이해가 어렵지 않을 것이다.
(참고. 2번 주석은 input 태그의 ref에 바로 넣어도 되지만 깔끔하게 정리하게 위해 함수로 밖에 뺐다.)

class GuGuDan extends React.Component {
    state = { 
        ....
    }    

    ....
    onSubmit = () => {
        ....

        this.input.focus(); // 3. input 태그 참조를 이용하여 focus
    }

    input; // 1. 멤버 변수로 선언

    onRefInput = (c) => {this.input = c;} // 2. input 태그의 참조를 input에 저장

    render() {
        return (
            <React.Fragment>
                <div>{this.state.first}곱하기{this.state.second}?</div>
                <form onSubmit={this.onSubmit}>
                    <input ref={this.onRefInput} type="number" value={this.state.value} onChange={this.onChange}/>
                    <button>입력!</button>
                    </form>
                <div>{this.state.result}</div>
            </React.Fragment>
        );
    }
}


두번째 방법 : createRef 사용
함수를 사용하지 않아도 돼서 좀 더 간결하고, 함수형과 거의 비슷하게 사용할 수 있다.
(말하자면 이것도 멤버변수에 넣는 방식인데 사용법이 좀 다르다.)

class GuGuDan extends React.Component {
    state = { 
        ....
    }    

    ....
    onSubmit = () => {
        ....

        this.inputRef.current.focus(); // 3. inputRef 이용 focus()
    }

    inputRef = React.createRef(); // 1. 멤버 변수에 createRef() 

    render() {
        return (
            <React.Fragment>
                <div>{this.state.first}곱하기{this.state.second}?</div>
                <form onSubmit={this.onSubmit}>
                    // 2. 여기서 inputRef를 바로 대입
                    <input ref={this.inputRef} type="number" value={this.state.value} onChange={this.onChange}/>
                    <button>입력!</button>
                    </form>
                <div>{this.state.result}</div>
            </React.Fragment>
        );
    }
}

위 두가지만 살펴보면, createRef()를 이용하는 것이 좀 더 간결하고 직관적으로 보인다.
하지만 함수를 이용해서 ref를 가져오는 방법은, 함수를 이용하기 때문에
참조를 가져올때 다른 동작을 추가적으로 실행시킬 수 있는 자유도를 어느정도 가지게 된다.

입맛에 맞게 사용하면 될 듯하다. 나라면 필요한 경우가 아니라면 createRef()를 사용할 것 같다.


2. 함수형 component

useRef()를 이용하면 된다. class에서의 createRef() 거의 흡사하기 때문에
자세한 설명은 생략한다. 코드로 충분히 즐기시길..

const GuGuDan = () => {
    const [first, setFirst] = useState('default');
    ....

    onSubmit = () => {
        ....

        this.inputRef.current.focus(); // 3. inputRef 이용 focus()
    }

    inputRef = React.useRef(null); // 1. 멤버 변수에 useRef(null) 
  
    return (
        <React.Fragment>
            <div>{first}곱하기{second}?</div>
            <form onSubmit={onSubmit}>
                // 2. 여기서 inputRef를 바로 대입
                <input ref={this.inputRef} type="number" value={value} onChange={onChange}/>
                <button>입력!</button>
                </form>
            <div>{result}</div>
        </React.Fragment>
    );
}

createRef()나 useRef()는 모두 사용할때 current를 중간에 껴줘야 한다는 점, 단디 하자.




+ useRef의 다른 사용법

이건 좀 다른 이야기 인데, 당연하지만 중요한 부분이라 생각한다.

제로초님의 리액트 무료강좌(웹게임) 4-4 를 보고 정리하는 것이다.

기본적으로 state, props가 변경되면 컴포넌트는 렌더링 된다. 따라서 불필요한 렌더링을 막기 위해서는
어떤 값이 state/props 인지 아닌지를 명확히 파악하여야 한다.

예를 들어보자

시작 버튼을 클릭한 순간부터, 끝 버튼을 클릭한 순간까지의 걸린 시간을 출력하려고 한다.
걸린 시간은 바뀌는 값이기 때문에 state가 분명할 것이다.
하지만 걸린 시간을 계산하기 위해서는 시작 시간과 끝 시간을 따로 저장해야 한다.

만약 state에 저장한다면 시작/끝 버튼을 누르는 순간마다 모두 렌더링 되기 때문에 낭비이다.
시작/끝 시간을 state/props가 아닌 다른 변수에 저장하면 될 것이다.

이렇게 계산이나 로직에는 필요하지만, 화면에는 렌더링되고 싶지 않은 값, 어떻게 할까?


class 컴포넌트의 경우, 너무 쉽다. 그냥 state 밖에다가 멤버변수 마냥 선언해주고 사용하면 끝

class Test extends React.Component {
    state = {
        ...,
        result: 0,
    }
    
    startTime;
    endTime;

    onStartClick = () => {
        ...,    
        this.startTime = new Date();
    }

    onEndClick = () => {
        ...,
        this.endTime = new Date();
        setState({
            result: this.endTime - this.startTime,
        });
    }

    ...,

}

그럼 함수형에서는? 여기서 useRef()가 등장한다.

const Test = () => {
    const [result, setResult] = React.useState(0);
    
    const startTime = useRef();
    const endTime = useRef();

    const onStartClick = () => {
        ...,    
        startTime.current = new Date();
    }

    const onEndClick = () => {
        ...,
        endTime.current = new Date();
        setResult(endTime.current - startTime.current);
    }

    ...,
}

dom에 접근하는 용도 말고도, 이런식으로 렌더링과 무관한 변하는 값을 useRef()를 이용해 사용할 수 있다.




포스팅을 하면서 신기한 점은, 정리를 하면서 얻어가는게 피부로 느껴진다는 점이다.
이번 포스팅의 계기만 하더라도, "ref 대충 알겠는데, class/함수형 헷갈리네? 정리하자" 였는데
지금 마지막 문단의 나는 '사용법'이 아니라 'ref'에 대해 확실히 알게 된듯한 느낌이 든다.

"ref를 사용하려면 ~이렇게 해야지"가 아니라
"dom에 접근할 일이 있으면 ref를 써야겠구나" 같이 나만의 규칙이 생긴 느낌 (나만의 규칙은 아니겠지만서도)

0개의 댓글