#back_to_basic
다시 처음부터!라는 마음으로 공부해보자! 아자아자!
1. 문제의 발단
프로젝트에서 useRef를 사용하여 input에 focus를 잡아야하는데, 이게 자꾸 inputRef.current = null로 잡혀 에러가 나는 것이다....
오늘은 이 에러도 해결해 볼 겸, 다시 useRef를 살펴보자.
const inputRef = useRef<HTMLInputElement>(null);
const handleTriggerClick = (_e: React.ClickEvent<HTMLButtonElement>) => {
inputRef.current?.focus(); // null로 잡혀서 안됨
}
<input type="text" placeholder="검색" type="text" ref={inputRef}/>
(위 코드는 null로 잡혀서 돌아가지 않는 코드의 일부분)
(위 코드는 기본적으로 맞으나, 응용 방법이 잘못되었음)
2. useRef란
useRef() Hook은 DOM ref만을 위한 것이 아닙니다. 본질적으로 useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 “상자”와 같습니다. 만약 <div ref={myRef} />를 사용하여 React로 ref 객체를 전달한다면, React는 모드가 변경될 때마다 변경된 DOM 노드에 그것의 .current 프로퍼티를 설정할 것입니다. useRef는 내용이 변경될 때 그것을 알려주지는 않는다는 것을 유념하세요. .current 프로퍼티를 변형하는 것이 리렌더링을 발생시키지는 않습니다.
- 리액트 공식문서
useRef는 간단히 말해서 렌더링이 필요하지 않은 값을 참조하기 위한 React Hook이다.
ref 안에는 항상 current라는 프로퍼티가 있고, 저장하는 값은 ref.current를 통해 접근 및 업데이트 할 수 있다.
아래처럼 초기화가 가능하다. 이 값은 초기 렌더링 이후부터는 무시된다.
const inputRef = useRef<HTMLInputElement>(null);
console.log(inputRef); // null
useRef는 언제 사용하는가?
- 변수 관리
- DOM 요소 선택(내가 하려는 것)
(자세한 예시는 여기 킹갓 블로그 글 참고)
1) 변수 관리
ref는 state와 비슷하게 값을 저장하는 저장공간으로도 사용이 되는데,
- state의 경우: state 변화 => 렌더링 트리거 => 컴포넌트 내부 변수들 초기화
- ref의 경우: ref의 변화 => No 렌더링 => 변수 값 유지
- state와 ref를 함께 사용하는 경우: state의 변화 => 렌더링 => 그래도 ref의 값은 유지
위 같은 특징 때문에, 변경 시 렌더링을 발생시키지 말아야하는 값을 다룰 때 사용한다.
내가 마주한 문제는 아니지만, '불필요한 렌더링을 줄인다'는 관점에서 보면
input에서 입력해서 달라지는 값들(화면상 업데이트 필요 없는 경우)은 ref로 관리도 가능하지 않을까...하는 생각이 든다.
(라는 생각을 코드로 구현한 이 블로그를 찾았다)
2) DOM 요소에 접근
const inputRef = useRef<HTMLInputElement>(null);
<input type="text" placeholder="검색" type="text" ref={inputRef}/>
원래는 getElementById, querySelector 같은 DOM Selector 함수를 사용해서 DOM을 선택해야했다.
엘리먼트의 크기를 가져오거나, 스크롤바 위치를 가져오거나, 포커스를 설정해야되거나 등의 경우가 해당되는데, 이 때 리액트에서는 ref라는 것을 사용할 수 있다.
위처럼 우리가 선택하고 싶은 DOM(input)에 ref 값으로 설정을 해주면, Ref 객체의 .current 프로퍼티는 우리가 원하는 DOM을 가르키게 된다.
3. 그렇다면 언제 useState, useRef를 구분해서 사용하는가
이 페이지를 참고하면,
결국 가장 큰 차이점은 '리렌더링'이다. useState는 리렌더링을 유발하고, useRef는 아니다.
공통점은 둘 다 리렌더링 이후에도 데이터를 기억할 수 있다는 것이다.
그렇다면, 결론은!
view layer render을 유발시키려면(결정하려면) => useState,
그게 아니라면 useRef
4. 그렇다면 문제는 무엇인가
문제에 대해 다시 생각해보았다.
사실 위 코드말고도 내 화면은 아래와 같은 플로우를 가진다.
1) 버튼을 클릭하면 select가 펼쳐진다(collapse)
2) 1번에서의 '펼쳐진다'는 state를 통해 관리된다
3) 펼쳐지면 거기 안에 있는 input에 focus를 한다.
여기서 내가 겪은 문제는 '펼쳐진다'를 처리하는 함수에서 inputRef.current.focus()를 통해 포커싱을 실행시켰음에도 불구하고, inputRef.current가 초기화된 값을 가지고 있어서 제대로 동작하지 않는다는 것이다.
위에서 말한대로
- state와 관계없이 ref는 유지
- 재렌더링을 유발하지 않음
에도 불구하고, 자꾸 초기값인 null이 나오는 것이 이해가 가지 않았다.
하지만 다시 위 플로우를 생각해보니,
1번에서 select가 펼쳐지는 것을 처리하기 위해 state가 true면 다른 dom element를 return해주고 있었다.
그렇다면........클릭하는 함수에서 state => true로 바꿔주고 있고, 동시에 focus()를 호출하기 때문에, 이때의 시점에서는 dom element가 return 되기 전이라서 null일 수 밖에 없었다!.....난 바보...
위 문제점은
useEffect를 통해서,
렌더링 이후 state가 true일 때(펼쳐진 경우, input이 있는 경우)에 focus()를 호출함으로써 해결하였다.
...
문제를 해결하는 과정에서 다시 관련 정보들을 찾고 정리하는 것이 도움이 많이 된다는 것을 새삼 느꼈다....
다음엔 useEffect도 다시 정리해볼 겸, 라이프사이클도 정리해봐야겠다.
References
- https://velog.io/@jinyoung985/React-useRef%EB%9E%80
- https://velog.io/@skawnkk/useState-vs-useRef
- https://devbirdfeet.tistory.com/274
- https://hihiha2.tistory.com/19
- https://react.vlpt.us/basic/10-useRef.html
화이팅이다!
'Web' 카테고리의 다른 글
[FE 기술 면접 준비] (0) | 2023.12.19 |
---|---|
[BB] React Life Cycle와 useEffect (0) | 2023.12.13 |
[BB] Javascript: 불변성(immutability) (0) | 2023.10.26 |
[BB] 브라우저의 렌더링과 Virtual DOM (1) | 2023.10.25 |
[BB] Javascript: 비동기 프로그래밍 (0) | 2023.10.25 |