[TIL] 2022-05-28 / (React - Hook 2)
미음제
·2022. 5. 28. 19:32
오늘 배운 내용
useRef
useRef는 언제 사용하는가? Javascript에서 DOM Selector 함수를 사용하여 DOM에 직접적인 접근을 하는 것처럼, React에서는 이 기능을 useRef가 대체하고 있다.
간단한 예제를 살펴보자.
import { useRef } from 'react';
function App() {
const inputRef = useRef(null);
return (
<>
<div style={{ display: 'flex', padding: 50}}>
<input ref={inputRef} type='text' />
<button onClick={() => inputRef.current.focus()}>Click Here!</button>
</div>
</>
);
}
export default App;
input 태그가 있고, button 태그가 있는 기본적인 구조다. button을 클릭하게 되면, input 태그에 focus를 주는 구조이다. Javascript에서 DOM에 접근하기 위해서는 queryselector 혹은 getelementbyid를 통해 접근할 수 있다.
React에서는 기본적으로 DOM에 대한 직접적인 접근을 막고 있어 useRef Hook을 이용해 DOM에 직접 접근할 수 있다.
useRef를 사용하기 위해 import를 하고, useRef(null)을 통해 초기에 선택된 값이 없을 때의 변수를 생성한다. 그리고 접근을 원하는 DOM에서 ref={변수}를 할당해, 해당 DOM에 대한 접근을 할 수 있다.
공식문서의 말을 빌려오면 다음과 같이 useRef를 설명하고 있다.
useRef는 .current 프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환합니다. 반환된 객체는 컴포넌트의 전 생애주기를 통해 유지될 것입니다.
본질적으로 useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 “상자”와 같습니다.
여기까지 살펴본 바로는 useRef는 접근하고 싶은 DOM 객체를 담을 수 있는 변수 정도라고 생각된다. 다만 강의 내용 초반에서 'React는 DOM에 대한 직접적인 접근을 막는다.'는 말이 와닿지 않았다. 강의 내용은 Function 기반의 Component라 Class 기반 Component의 문서가 직접적인 해답은 아닐지라도, React에서 크게 차이를 두고 있지 않다고 했으니 설명을 빌려와 이해하려고 했다.
우선 설명을 보자면 다음과 같다.
DOM node에 대한 참조를 저장하기 위해 ref(Function 기반에서는 useRef Hook이겠죠?)를 사용합니다.
컴포넌트가 마운트될 때 React는 current 프로퍼티에 DOM 엘리먼트를 대입하고, 컴포넌트의 마운트가 해제될 때 current 프로퍼티를 다시 null로 돌려 놓습니다. ref를 수정하는 작업은 componentDidMount 또는 componentDidUpdate 생명주기 메서드가 호출되기 전에 이루어집니다.
이 말을 빌려와 예제를 이해해보면 다음과 같이 말할 수 있을 것 같다. 최초 컴포넌트가 생성됨과 동시에 null로 useRef를 초기화 해둔다. 그리고 컴포넌트가 mounted 될 때 null이 할당된 식별자에 DOM node로 재할당한다. 그리고 컴포넌트가 unmounted 될 때 다시 null로 해제하게 된다.
useRef의 동작 방식은 어느정도 이해가 된 것 같다. 그럼에도 직접적인 DOM 접근을 막고, ref나 useRef Hook을 쓰는 이유는 명확해지지 않은 것 같다.
여러 블로그를 참고하고, 지난 시간에 배운 내용을 복기해보면 React는 실제 DOM을 바로 사용하기보단 가상 DOM을 사용하기 때문이라고 이해했다.
즉, 컴포넌트가 생성되고 mounted 되기 전까지 실제 DOM은 존재하지 않는 것이니까 Javascript에서 사용하는 DOM Selector 함수로의 접근을 지양하는 것이라고 생각할 수 있다. Class 기반에서 ref, Function 기반에서 useRef로 접근하는 시점은 가상 DOM이 생성이 완료되고 실제로 mounted 된 시점이다. useRef의 초기 값으로 null로 넘겨준 것은 mounted 되기 전까지 접근할 DOM이 없기 때문이고, mounted 된 시점에서 DOM에 직접적인 접근이 가능해져 해당 DOM을 할당하는 것이다.
function App() {
return (
<>
<div style={{ display: 'flex', padding: 50}}>
<input className='input' type='text' />
<button onClick={() => document.querySelector('.input').focus()}>Click Here!</button>
</div>
</>
);
}
export default App;
그러나 useRef를 사용하지 않고, DOM Selector 함수를 통해 직접 접근해도 동작은 제대로 동작한다.
아직 명확하게 이해하진 못했지만 나름대로 생각해보면 이유는 다음과 같다고 생각된다.
- React는 가상 DOM을 신뢰한다. (mounted 되기 전까지 해당 DOM이 진짜로 있는지 모르기 때문)
- state와 props의 변경이 있는 경우 re-rendering 된다.
- state는 해당 컴포넌트 내부에서만 제어를 한다.
- 따라서 DOM에 대한 직접적인 접근은 React의 영영 밖(정확한 표현을 모르겠다..)이다.
"동작은 되지만 좋은 접근 방식은 아니라는 것. 권장하고 있는 방식이 있다면 따를 것."
state를 컴포넌트에 한정지어 사용하고 있어(useState) DOM 제어 역시 컴포넌트 내부에서 제어할 수 있도록 제한한 것이 아닐까 싶다. 현재까지 이해할 수 있는 내용은 이 정도인 것 같다.
지금까지 useRef의 대략적인 사용법과 이해에 대한 내용이었다. useRef는 신기한 특징이 있다. useState는 값이 변경된 경우 리렌더링을 하지만, useRef는 값이 변경되더라도 리 렌더링 되지 않는다.
다시 처음 예제로 돌아와 useEffect Hook을 이용해 렌더링이 몇 번 일어나는지 확인해보자.
import { useRef, useEffect } from 'react';
function App() {
const inputRef = useRef(null);
useEffect(() => {
console.log('rendering');
});
return (
<>
<div style={{ display: 'flex', padding: 50}}>
<input ref={inputRef} type='text' />
<button onClick={() => inputRef.current.focus()}>Click Here!</button>
</div>
</>
);
}
export default App;
useEffect Hook은 최초 컴포넌트가 생성되면서 내부 로직을 수행할 것이다. useState Hook을 사용했을 때, 값이 변경되면 useEffect Hook 내부 내용이 계속해서 실행되었었다. 그러나 useRef Hook을 통해 값이 변경되더라도 다시 렌더링 되지 않는다.
만약 컴포넌트 내부에서 지역 변수를 사용할 때, useState를 통해 사용하면 state 값의 변경이 있을 때마다 리렌더링된다. 값은 변경하지만 리 렌더링을 원하지 않는 경우, useRef를 통해 지역 변수를 사용하면 리 렌더링을 하지 않을 수 있다.
아직 정확한 예제를 찾지 못했고 정확한 이해가 부족하지만, 대략적으로 이해한 느낌은 다음과 같다.
- 값이 변경되더라도 useRef는 useState와 달리 리렌더링 되지 않는다.
- 리 렌더링을 위한 값으로는 useState를 사용한다.
- 리 렌더링을 원하지 않는 경우 useRef를 사용한다.
useRef는 DOM에 대한 접근 외에도 컴포넌트에 대한 접근이 가능하다.
// Input 컴포넌트
const Input = () => {
return <input type='text' placeholder="input"/>
};
export default Input;
import { useRef } from 'react';
import Input from './components/Input';
function App() {
const inputRef = useRef(null);
return (
<>
<div style={{ display: 'flex', padding: 50}}>
<Input ref={inputRef}/>
<button onClick={() => inputRef.current.focus()}>Click Here!</button>
</div>
</>
);
}
export default App;
App 컴포넌트 내부에서 input DOM에 직접 접근하듯 사용해보면 다음과 같은 에러가 발생한다.
에러 메세지에서 컴포넌트에 접근하고 싶다면 어떻게 하라고 친절하게 설명해준다. Function 기반에서는 ref를 지원해주고 있지 않기 때문에 에러 메시지에 출력된 방법을 사용하라는 것이다.
Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
React.forwardRef()를 사용해 접근을 하라고 한다.
// Input 컴포넌트
import React from 'react';
const Input = React.forwardRef((_, ref) => {
return <input ref={ref} type='text' placeholder="input"/>
})
export default Input;
import { useRef } from 'react';
import Input from './components/Input';
function App() {
const inputRef = useRef(null);
return (
<>
<div style={{ display: 'flex', padding: 50}}>
<Input ref={inputRef}/>
<button onClick={() => inputRef.current.focus()}>Click Here!</button>
</div>
</>
);
}
export default App;
ref를 전달하는 방법은 React.forwardRef() 메서드를 활용한다.
ref를 넣어주면 바깥쪽에서 attribute를 통해 ref를 넘겨주는 것을 컴포넌트에 자동으로 연결해 준다.
React.forwardRef() 메서드를 통해 ref를 하위 컴포넌트로 전달해준다. 하위 컴포넌트에서 Function을 React.forwardRef()로 한 번 감싸고 하위 컴포넌트로 부모로부터 받은 props와 ref를 내려준다(props를 전달하고 있지 않아 첫 번째 인자로 '_'를 넘겼다).
ref를 넘겨주면 바깥쪽에서 attribute를 통해 ref를 넘겨주는 것을 컴포넌트에 연결해 준다.
오늘의 회고
어제에 이어 오늘도 Hook에 대해 공부했다. useRef에 대해 이해가 안가는 부분을 서칭 해보고 비교해보면서 나름 정리를 했다. 대략적인 감을 잡고 이해는 했는데, DOM에 대한 직접적인 접근을 지양한다는 부분에 대해 관련 내용을 찾지 못해 아직까진 이해가 완벽하진 않다. 친구의 말을 빌리자면 새로고침 시 실제 DOM에 대한 접근과 가상 DOM(useRef)에 대한 접근에 차이가 있다고 하는데, 새로고침을 하면 useRef도 초기화되니깐 결과론적으로 똑같은 결과가 아닌가 싶다. 이 부분에 대해서는 스크럼 때 질문하면서 이해를 해봐야 할 듯싶다.
참고
강의 : 프로그래머스 데브코스 프론트엔드 2기
'프로그래머스 > 데브코스 프론트엔드' 카테고리의 다른 글
[TIL] 2022-06-23 / Day 69 (타입스크립트 기본 문법) (0) | 2022.06.23 |
---|---|
[TIL] 2022-06-01 / 53일차 (React - Hook 3) (0) | 2022.06.01 |
[TIL] 2022-05-27 / 50일차(React - Hook 1) (0) | 2022.05.28 |
[TIL] 2022-05-25 / 48일차(React) (0) | 2022.05.25 |
[TIL] 2022-04-29/05-01 / 29, 30일차 (0) | 2022.05.02 |