[ React ] React Hooks - useRef

     

     

    useRef란? 

    렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook로

    컴포넌트의 최상위 레벨에서 useRef를 호출하여 ref를 선언한다.

     

    const ref = useRef(initialValue)

    initialValue

    - ref객체의 current  프러퍼티 초기 설정값으로 어떤 유형의 값이든 지정할 수 있다.

    - 단 초기 렌더링 이후부터는 무시된다.

     

    반환값

    - 단일 프로퍼티를 가진 객체를 반환한다.

    - current

        처음에는 전달한 initialValue로 설정된다.

        값을 변경할 수 있다.

        하지만 state와 다르게 리렌더링을 유발하지않는다.

        ref객체를 jsx노드의 ref 어트리뷰트로 react에 전달하면 react는 current 프로퍼티를 설정한다.

    - 다음 렌더링에서 useRef는 동일한 객체를 반환합니다.

     

     

     

    왜 사용할까?

    1. 리렌더링 사이에 정보를 저장할 수 있다.

    → 일반변수는 렌더링 할 때마다 재설정된다.

    2. 변경을 해도 리렌더링을 촉발하지않는다.

    → state는 값을 변경할 때마다 리렌더링을 촉발한다.

    3. 컴포넌트에 로컬로 저장된다.

     

     

     

    ref로 DOM조작하기

    1. 초기값이 null인 ref 객체 선언하기

    import { useRef } from 'react';
    
    function MyComponent() {
      const inputRef = useRef(null);
      // ...

     

    2. ref 객체를 ref속성으로 조작하려는 DOM 노드의 JSX에 전달하기

      return <input ref={inputRef} />;

     

    3. React가 DOM 노드를 생성하고 화면에 그린 후, React는 ref객체의 current 프로퍼티를 DOM노드로 설정한다.

    그렇게되면 DOM노드에 접근해 메서드등을 호출할 수 있다.

     function handleClick() {
        inputRef.current.focus();
      }

     

    * 노드가 화면에서 제거되면 React는 current 프로퍼티를 다시 null로 설정한다.

     

    완성코드

    import { useRef } from 'react';
    
    export default function Form() {
      const inputRef = useRef(null);
    
      function handleClick() {
        inputRef.current.focus();
      }
    
      return (
        <>
          <input ref={inputRef} />
          <button onClick={handleClick}>
            Focus the input
          </button>
        </>
      );
    }

     

     

     

    부모 컴포넌트에서 자식 DOM 조작하기

    옳지못한 코드

    React는 기본적으로 다른 컴포넌트의 DOM 노드에 접근하는 것을 허용하지 않는다.

    접 다른 컴포넌트의 DOM 노드를 조작하는 것은 코드가 쉽게 깨지게 만들기때문이다.

    import { useRef } from 'react';
    
    function MyInput(props) {
      return <input {...props} />;
    }
    
    export default function MyForm() {
      const inputRef = useRef(null);
    
      function handleClick() {
        inputRef.current.focus();
      }
    
      return (
        <>
          <MyInput ref={inputRef} />
          <button onClick={handleClick}>
            Focus the input
          </button>
        </>
      );
    }

     

     

    하지만 특정 컴포넌트에서 소유한 DOM 노드를 선택적으로 노출할 수 있다.

    컴포넌트는 자식 중 하나에 ref를 전달하도록 지정할 수 있다.

     

     

    옳은 코드

    1. <MyInput ref={inputRef} />으로 React가 대응되는 DOM 노드를 inputRef.current에 대입하도록 설정한다.
    2. MyInput 컴포넌트는 forwardRef를 통해 선언되었다.
      이를 통해 props 다음에 선언된 두 번째 ref 인수를 통해 상위의 inputRef를 받을 수 있도록 합니다.
    3. MyInput은 자체적으로 수신받은 ref를 컴포넌트 내부의 <input>으로 전달합니다.
    import { forwardRef, useRef } from 'react';
    
    const MyInput = forwardRef((props, ref) => {
      return <input {...props} ref={ref} />;
    });
    
    export default function Form() {
      const inputRef = useRef(null);
    
      function handleClick() {
        inputRef.current.focus();
      }
    
      return (
        <>
          <MyInput ref={inputRef} />
          <button onClick={handleClick}>
            Focus the input
          </button>
        </>
      );
    }

     

     

     

    왜 null로 초기화할까?

    React는 초기에 ref 값을 한 번 저장하고, 다음 렌더링부터는 이를 무시한다.

     

    아래와 같이 작성하면 다음과 같은 문제가 발생한다.

     

    • 불필요한 인스턴스 생성
      렌더링이 일어날 때마다 불필요하게 새로운 인스턴스를 생성한다.
      이는 성능 문제로 이어질 수 있다.
    • 일관성 문제
      useRef는 일반적으로 동일한 객체를 참조하도록 설계되어 있지만,
      렌더링이 발생할 때마다 다른 객체를 참조하게 된다.
      이는 컴포넌트의 동작에 예기치 않은 문제를 일으킬 수 있다.
    function Video() {
      const playerRef = useRef(new VideoPlayer());
      // ...

     

     

    그렇기에 다음과 같이 초기화하는 것 ! 

    function Video() {
      const playerRef = useRef(null);
      if (playerRef.current === null) {
        playerRef.current = new VideoPlayer();
      }
      //...

     

     

     

     

    주의사항

    1. ref.current 프로퍼티는 state와 달리 변이가 가능하다.

        단 렌더링에 사용되는 객체를 포함하는 경우 해당 객체를 변이해서는 안된다.

     

    2.ref.current 프로퍼티를 변경해도 React는 컴포넌트를 다시 렌더링하지 않는다.

        ref는 일반 JavaScript 객체이기 때문에 React는 사용자가 언제 변경했는지 알지 못한다.

     

    3.Strict Mode에서 React는 컴포넌트 함수를 두 번 호출하여 의도하지않은 변경을 찾을 수 있도록 돕는다.

       이는 개발 환경 전용 동작이며 Production 환경에는 영향을 미치지 않는다.

       각 ref 객체는 두 번 생성되고 그중 하나는 버려진다.

     

    4. 렌더링 중에는 ref.current를 쓰거나 읽지않아야한다. → 대신 state 사용하기

    function MyComponent() {
      // ...
      myRef.current = 123;
      // ...
      return <h1>{myOtherRef.current}</h1>;
    }

     

    대신 이벤트 핸들러나 Effect에서 ref를 읽거나 쓸 수 있다.

    function MyComponent() {
      // ...
      useEffect(() => {
        myRef.current = 123;
      });
      // ...
      function handleClick() {
        doSomething(myOtherRef.current);
      }
      // ...
    }

     

     

     

     

    [참고]

    https://ko.react.dev/reference/react/useRef

    댓글