[React] useRef, forwardRef - 기록

TH_velog·2024년 1월 30일
1

React

목록 보기
16/16
post-thumbnail

📌 useRef

📖 useRef란?

  • 렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook
  • 리액트 훅의 한 종류로 Ref는 reference(참조)의 줄임말.
  • 저장공간 또는 DOM요소에 접근하기 위해 사용되는 React Hook

📗 import / useRef(초깃값)

import { useRef } from 'react';
const testRef = useRef(초깃값);

✅ 초깃값 0 - console.log 확인

  • useRef는 단일 프로퍼티를 가진 객체를 반환하고 current 처음 제공한 초깃값으로 설정이 되며 변경이 가능합니다.
  • current 프로퍼티 초기 설정값으로 어떤 유형의 값이든 지정이 가능

📗 useRef 사용

📘 특징

  • ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데 적합합니다.
  • current 프로퍼티를 읽고, 변경, 사용 👉 ref.current / ref.currenf = "변경 값"
  • current 프로퍼티를 변경해도 컴포넌트는 다시 렌더링하지 않습니다.
  • 재렌더링 하지 않아 불필요한 렌더링을 방지할 수 있습니다.
  • DOM 요소 접근의 대표적으로 input 요소에 focus를 사용할 때

⚠️
❌ 렌더링 중에는 ref를 작성 또는 쓰거나 읽지 마세요 🤔
EX)

const testRef = useRef(0);
// ❌
testRef.current = 123456789;
return (
  <>
    <p>- {testRef.current}</p>
  </>
)
  • React는 컴포넌트의 본문이 순수 함수처럼 동작하기를 기대합니다
  • 입력값들(props, state, context)이 동일하면 완전히 동일한 JSX를 반환해야 합니다.
  • 다른 순서나 다른 인수를 사용하여 호출해도 다른 호출의 결과에 영향을 미치지 않아야 합니다.
  • 렌더링 중에 ref를 읽거나 쓰면 이러한 기대가 깨집니다.

👇 👇 👇

// 🟢
const testRef = useRef(0);
const [myRef, setMyRef] = useState(0);
// 🟢
useEffect(() => {
  // useEffect에서 ref를 읽거나 변경 가능!!
  testRef.current = 123456789;

});
const refChange = () => {
  // 이벤트 핸들러에서 ref를 읽거나 변경 가능!!
  setMyRef(testRef.current)
}
return (
  <div>
  	{/* 읽거나 사용해야 한다면 state를 사용 */}
    <p>🟢 - {myRef}</p>
    <button type="button" onClick={refChange}>useRef 변경</button>
  </div>
)
  • 렌더링 중에 무언가를 읽거나 써야만 하는 경우, 대신 state를 사용!!
  • 컴포넌트는 이러한 규칙을 어기더라도 여전히 작동할 수도 있습니다.
  • React에 추가되는 대부분의 새로운 기능들은 이러한 기대에 의존합니다.

📘 state, ref, let 값 변경 비교

const [state, setState] = useState(0);
const ref = useRef(0);
let count = 0;

function stateClick() {
  setState((prev) => prev + 1);
  console.log('state:', state);
}
function refClick() {
  ref.current = ref.current + 1;
  console.log('ref', ref.current);
}
function letClick() {
  count = count + 1;
  console.log('let', count);
}

console.log("렌더링 확인!")
return (
  <div>
    <div>
      <p>state 👉 {state}</p>
      <p>변경: 렌더링! 변경 값 노출</p>
      <button type="button" onClick={stateClick}>state Click</button>
    </div>
    <hr />
    <div>
      <p>ref 👉 {ref.current}</p>
      <p>변경: 렌더링 되지 않아 변경 값 노출 X, 컴포넌트 렌더링 될 때 반영</p>
      <button type="button" onClick={refClick}>ref Click</button>
    </div>
    <hr />
    <div>
      <p>let 👉 {count}</p>
      <p>변경: 렌더링 X, 렌더링 시 반영 값 X : 초깃값으로 재시작</p>
      <button type="button" onClick={letClick}>let Click</button>
    </div>
  </div>
)

  • state : 재렌더링 🟢 컴포넌트 재렌더링으로 증가 값 업데이트 확인. ref의 값도 재렌더링 시 업데이트가 된다. 🟢
  • ref.current : 재렌더링 ❌ 값은 정상적으로 증가, 렌더링이 되지 않기에 화면에 업데이트 되지 않는다 state 값 변경으로 재렌더링 시 화면에 업데이트 🟢
  • let : 재렌더링 ❌ console 값 증가 확인, 재렌더링 시 초깃값으로 시작 👉 화면에는 결국 초깃값이 나온다. ❌

📘 interval ref 사용

const [time, setTime] = useState(0);
const intervalRef = useRef(null);

function timeStart() {
  intervalRef.current = setInterval(() => {
    setTime(prev => prev+1);
  }, 100);
}
function timeStop() {
  clearInterval(intervalRef.current);
}
return (
  <>
    <p>{time}</p>
    <button type="button" onClick={timeStart}>시작</button>
  	<button type="button" onClick={timeStop}>정지</button>
  </>
)
  • ref.current로 Interval 정보를 저장하고, state 값이 변경하여 재렌더링이 되어도 저장된 interval 값을 제어를 할 수 있어서 clearInterval 사용 가능!

📘 DOM 요소 접근 EX - input focus

const inputRef = useRef();
const aRef = useRef();

function inputFocus(){
  console.log(inputRef.current); // <input type="text">
  inputRef.current.focus();
}
function aFocus(){
  console.log(aRef.current); // <a href="/">a 태그</a>
  aRef.current.focus();
}
return (
  <div>
    <input 
      type="text" 
      ref={inputRef}
    />
    <a 
	  href="/" 
	  ref={aRef}
	>
      a 포커스
    </a>
    <hr />
    <button type="button" onClick={inputFocus}>input focus 이동</button>
    <button type="button" onClick={aFocus}>a focus 이동</button>
  </div>
)
  • input focus 이동 버튼을 클릭 시 input으로 포커스 이동.
  • a focus 이동 버튼을 클릭 시 a 태그로 포커스 이동

📗 forwardRef

📍 컴포넌트에 ref prop을 넘겨서 그 내부에 있는 DOM 엘리먼트에 접근을 하게 해주는 forwardRef() 함수.

  • 함수형 컴포넌트를 forwardRef로 감싸주어 ref를 사용할 수 있다.
import { forwardRef } from 'react';

const InputTest = forwardRef(function InputTest(props, ref) {
  return <input {...props} ref={ref} />;
});

☝️ 사용 방법

📘 서로 다른 컴포넌트 ref, forwardRef 사용

✔️ 부모 컴포넌트에서 Input(자식 컴포넌트) Button 컴포넌트(자식 컴포넌트)를 사용하여 focus 제어.

// 🌟 부모 컴포넌트
import { useRef } from "react";
import RefBtn from "./RefBtn";
import RefInput from "./RefInput";

function RefForwardRef(){
  const inputRef = useRef();
  return (
    <>
      <p>📍 자식 RefInput 컴포넌트의 input을 ref 사용하여 접근</p>
      <RefInput ref={inputRef} /> {/* 👈 input 접근 */}
      <RefBtn focusEl={inputRef}/> {/* ref에 저장된 input으로 focus */}
    </> 
  )
}
export default RefForwardRef;

// 🌟 RefInput.jsx
import { forwardRef } from "react";

function RefInput (props,ref){
  return (
    <>
      <input type="text" ref={ref} />
    </> 
  )
}
export default forwardRef(RefInput); // 👈 forwardRef 사용하여 컴포넌트를 감싸준다.

// 🌟 RefBtn.jsx
function RefBtn({focusEl}){
  function focusClcik(){
    focusEl.current.focus();
  }
  return (
    <>
      <button type="button" onClick={focusClcik}>click</button>
    </> 
  )
}
export default RefBtn;

✅ 정상적으로 포커스 이동이 되는 것을 확인할 수 있습니다.

✍️ 끝.

감사합니다. 😁

참고
🔗 useRef-React

profile
공부&기록

0개의 댓글