React Hooks - useRef

jiny·2022년 9월 6일
0

React hooks

목록 보기
3/6
post-thumbnail

목차

  • useRef
  • useRef 사용 목적
  • useRef를 이용한 저장 공간 생성
  • useRef를 이용한 DOM 요소 접근

useRef

React docs

  • useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
  • Note that useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
  • 공식문서에 나와있는 useRef에 대한 설명
  • 첫 번째 설명에 대해 더 자세히 알아보자

useRef의 3가지 call Signature

1. initialValue가 존재할 경우

  • initialValue가 존재할 경우의 useRef

  • index.d.ts에 있는 useRef의 call signature
  • initialValue을 받아 MutableRefObject 를 반환

  • MutableRefObject는 말 그대로 javascript Object 라는 의미
  • Object 안엔 current property 존재
  • current property - 초기값을 넣었을 때 존재 하는 value

2. 초기값이 null

  • convenience overload for refs given as a ref prop as they typically start with a null value useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue).
  • Usage note: if you need the result of useRef to be directly mutable, include | null in the type of the generic argument.
  • 초기값이 null일 경우의 공식문서의 설명

  • 초기값이 null일 경우의 useRef

  • initialValue가 존재하지 않을 경우의 call signature
  • RefObject를 반환

  • RefObject도 마찬가지로 javascript Object
  • current property가 readonly로 되어있기 때문에 변경이 불가능

  • 실제로 변경이 되지 않음

  • 하지만 useRef의 지네릭으로 null이외에 다른 타입을 넘겨줄 경우 변경이 가능 (앞에 적어놓은 공식문서 설명의 2번째 부분)

3. 초기값이 비어있는 경우

  • 초기값이 비어있을 경우의 useRef

  • 마찬가지로 MutableRefObject를 반환 -> 변경이 가능

useRef의 특징

  • useRef는 3가지의 call signature를 제공
  • null이 들어갈 경우 readonly인 RefObject를 반환 하며 current 프로퍼티의 변경이 불가능하지만 지네릭으로 따로 설정할 경우 변경 가능
  • 그 외의 경우 MutableRefObject를 반환하며 변경이 가능
  • 반환된 object는 컴포넌트의 모든 라이프 사이클 동안 유지됨

useRef를 이용한 저장 공간 생성

  • 공식 문서의 2번째 설명에 대해 자세히 알아보자

app.tsx

import { useRef } from "react";

function App() {

  const countRef = useRef(0);

  const handleRefIncrease = () => {
    countRef.current += 1;
    console.log(countRef);
  }

  return (
    <>
      {countRef.current}
      <button onClick={handleRefIncrease}>Ref</button>
    </>
  );
}

export default App;

  • ref의 value와 버튼이 존재
  • 버튼을 클릭하면 ref 객체의 current 프로퍼티가 1 증가

실행 결과

  • Ref를 클릭할 경우 console에 ref 객체가 출력 된 것을 확인
  • current 프로퍼티에 증가 된 값이 보여지는 것을 확인
  • 하지만 브라우저에는 값이 증가되지 않은 것을 확인

이유

  • ref 객체는 state와 달리 재 렌더링을 발생시키지 않기 때문
  • 즉, .current 프로퍼티에 변경 가능한 값을 담고 있는 상자와 같음
  • 그러므로 useState처럼 컴포넌트 내의 변수 값을 조회, 수정하는 방법으로도 사용이 가능

useRef를 이용한 DOM 요소 접근

app.tsx

import { MutableRefObject, useRef } from "react";

function App() {
  const inputRef = useRef<HTMLInputElement>();
  console.log(inputRef);
  return (
    <>
      <input ref={inputRef as MutableRefObject<HTMLInputElement>} type="text" placeholder="username"/>
    </>
  );
}

export default App;
  • ref 속성을 통해 input의 dom 접근이 가능
  • inputRef를 console에 출력하여 확인

  • current의 value가 input 객체 인것을 확인
  • 즉, DOM 요소에 접근이 된 것을 확인

app.tsx

import { MutableRefObject, useRef } from "react";

function App() {
  const inputRef = useRef<HTMLInputElement>();
  const handleUserAuth = () => {
    let user = inputRef.current?.value as string;
    alert(`환영합니다. ${user}님`)
    inputRef.current?.focus();
  }
  return (
    <>
      <input ref={inputRef as MutableRefObject<HTMLInputElement>} type="text" placeholder="username"/>
      <button onClick={handleUserAuth}>login</button>
    </>
  );
}

export default App;
  • login 버튼을 누를 경우 handleUserAuth 함수가 실행 되면서 inputRef의 current value값이 알람에 뜸
  • focus 함수를 통해 알람을 누르면 input에 focus됨

실행 결과

  • input에 홍길동 입력

  • current의 value가 출력된 것을 확인

  • input에 focus 된 것을 확인

useState와 useRef의 비교

  • 좀 더 와닿는 비교를 위해 두 hook 비교

app.tsx

import { useEffect, useRef, useState } from "react";

function App() {

  useEffect(() => {
    console.log("컴포넌트 재 렌더링 중"); 
  })

  const countRef = useRef(0);

  const [count, setCount] = useState(0);

  const handleCountIncrease = () => {
    setCount(count + 1);
  }

  const handleRefIncrease = () => {
    countRef.current += 1;
    console.log(countRef)
  }

  return (
    <>
      {countRef.current}
      <button onClick={handleRefIncrease}>Ref</button>
      {count}
      <button onClick={handleCountIncrease}>count</button>
    </>
  );
}

export default App;

  • 두 저장공간을 위한 hook이 존재 (useRef, useState)
  • count 버튼을 클릭하면 state가 1 증가 하여 브라우저가 재 렌더링
  • 재 렌더링 되는지 확인하기 위해 useEffect 추가
  • ref 버튼을 클릭하면 current 프로퍼티가 1 증가

실행 결과

  • ref 버튼을 3번 누를 경우 current 값이 3 증가 했지만 재 렌더링이 발생하지 않아 화면엔 0으로 보임

  • count 버튼을 누르자 화면이 재 렌더링 되어 current의 최신 value가 화면에 그려진 것을 확인
  • 화면이 재 렌더링 되었기 때문에 useEffect가 실행되어 console에 "컴포넌트 재 렌더링 중"이 출력된 것을 확인
  • count가 1 증가 한 것을 확인

그럼 useState과 useRef는 언제 사용?

In a controlled component, form data is handled by a React component.
The alternative is uncontrolled components, where form data is handled by the DOM itself.

  • 공식문서의 설명
  • form의 input 상태같은 경우 React가 추적해서 그 값으로 어떤 행동을 할 여지가 있음.
  • 예를 들어 로그인 유효성 검사 로직이 state가 변함에 따라 실행되어야할 경우 제어 컴포넌트 활용 가능
  • 반면, 비제어 컴포넌트같은 경우 실제 DOM을 참조해야하는 경우에 필요.
  • 가장 흔한 경우로 input에 focus를 하는 상황

대부분 경우에 폼을 구현하는데 제어 컴포넌트를 사용하는 것이 좋습니다. 제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어집니다. 대안인 비제어 컴포넌트는 DOM 자체에서 폼 데이터가 다루어집니다.

  • 폼을 구현 할 땐 useState를 이용
  • 그 외에 HTML element의 DOM에 접근 할 경우 useRef을 사용
  • 말이 잘 안와닿으니 제어 컴포넌트와 비제어 컴포넌트에 대해 알아보자

제어 컴포넌트 & 비제어 컴포넌트

제어 컴포넌트

  • HTML에서 input, textarea, select와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트합니다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트됩니다.
  • 우리는 React state를 “신뢰 가능한 단일 출처 (single source of truth)“로 만들어 두 요소를 결합할 수 있습니다. 그러면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다.
  • React 공식 문서의 설명
export default function App() {
  const [input, setInput] = useState("");
  const onChange = (e) => {
    setInput(e.target.value);
  };

  return (
    <div className="App">
      <input onChange={onChange} />
    </div>
  );
}
  • 즉, 사용자의 입력을 받는 컴포넌트에 event 객체를 이용해 setState()로 값을 저장하는 방식
  • 제어 컴포넌트는 사용자가 입력한 값과 저장되는 값이 실시간으로 동기화

비제어 컴포넌트

  • 기존의 바닐라 자바스크립트와 크게 다르지 않은 방식
  • 바닐라 자바스크립트를 사용하며 폼을 제출할 때 (ex)submit button을 클릭할 때) 요소 내부의 값을 얻음
  • 비제어 컴포넌트 또한 이와 유사한 방식으로 사용된다.

비제어 컴포넌트 방식을 사용할 땐, 제어 컴포넌트 방식에서 사용한 setState()를 쓰지 않고 ref를 사용해서 값을 얻음

xport default function App() {
  const inputRef = useRef(); // ref 사용
  const onClick = () => {
    console.log(inputRef.current.value);
  };

  return (
    <div className="App">
      <input ref={inputRef} />
      <button type="submit" onClick={onClick}>
        전송
      </button>
    </div>
  );
}
  • 비제어 컴포넌트는 값이 실시간으로 동기화 되지 않음
  • 만약 a와 b라는 컴포넌트가 있을 때, a에 대한 변화를 즉각적으로 b가 영향을 받아야 할때 비제어 컴포넌트는 이런 방식에 대한 대응을 할 수 없음
  • 제어 컴포넌트의 경우 사용자가 입력을 하는 액션을 취할때마다 재 렌더링을 발생시킴
  • 반면, 비제어 컴포넌트는 사용자가 직접 트리거 하기 전까지는 재 렌더링을 발생시키지도 않고 값을 동기화 시키지도 않음

useRef가 재 렌더링을 발생시키지 않는 이유

  • useRef는 힙 영역에 저장되는 일반적인 자바스크립트 객체
  • 매번 렌더링할 때 동일한 객체를 제공한다. heap에 저장되어 있기 때문에 어플리케이션이 종료되거나 가비지 컬렉팅될 때 까지, 참조할때마다 같은 메모리 값을 가짐
  • 즉, 값이 변경되어도 리렌더링이 되지 않는데 그 이유는 같은 메모리 주소를 갖고있기 때문에 자바스크립트의 === 연산이 항상 true 를 반환
  • 변경사항을 감지할 수 없어서 리렌더링을 하지 않는다는 뜻

제어 컴포넌트 VS 비제어 컴포넌트

대부분 경우에 폼을 구현하는데 제어 컴포넌트를 사용하는 것이 좋습니다. 제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어집니다. 대안인 비제어 컴포넌트는 DOM 자체에서 폼 데이터가 다루어집니다.

  • useState와 useRef를 언제 사용하는지에 대해 아까 한번 언급했었는데, 과연 언제 사용하는 것이 적절할까?!
  • 제어 컴포넌트 & 비제어 컴포넌트가 되는 것과 안되는 것을 파악해보자

  • 즉각적으로, 실시간으로 값에 대한 피드백이 필요? => 제어 컴포넌트(useState 활용) 사용
  • 즉각적인 피드백이 불 필요 & 제출 시 값이 필요 & 불 필요한 렌더링과 값 동기화가 싫다 => 비 제어 컴포넌트 사용(useRef 활용)

비제어 컴포넌트를 사용해 렌더링을 최적화 하는 라이브러리
react-hook-form

레퍼런스

별코딩님 - useRef

https://www.youtube.com/watch?v=EMK8oUUwP5Q

hyunjine - useState vs useRef

https://velog.io/@hyunjine/useState-vs-useRef

yukyung - React: 제어 컴포넌트와 비제어 컴포넌트의 차이점

https://velog.io/@yukyung/React-%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0#%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%8A%94-%EB%90%98%EA%B3%A0-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%8A%94-%EC%95%88%EB%90%98%EB%8A%94-%EA%B2%83%EB%93%A4

0개의 댓글