React | useRef | ref | useState와 비교 | 사용자 지정 컴포넌트에서 ref 프롭을 바로 사용하기

이온·2023년 9월 21일
0

react

목록 보기
2/6
post-thumbnail

📍 ref란?

다른 DOM 요소에 접근해서 그것들로 작옵할 수 있게 해주는 것. 래퍼런스.
ref를 사용하여 마지막에 랜더링 되는 HTML요소들과 다른 자바스크립트 코드의 연결을 설정할 수 있다.

🚀 ref의 사용 방법

1️⃣ 이를 위해 먼저 ref를 생성해야한다. -> useRef의 사용!

const nameInputRed = useRef();

2️⃣ 그 다음 ref를 HTML 요소(ex. input 태그)에 연결하고 싶다고 자바스크립트에 알려주면 된다. ref를 연결하려는 해당 요소(ex. input 태그)로 이동해서 거기에 ref프롭을 추가한다.

<input
  ...
  ref={nameInputRef}
/>

3️⃣ 이제 연결이 설정 된 것이다. -> 즉 리액트가 해당 요소의 코드에 도달해서 랜더링 할 때 nameInputRef에 저장된 값을 이 인풋을 기반으로 랜더링 된 네이티브 DOM요소에 설정한다. = nameInputRef 안에 들어있는 것은 나중에 실제 DOM 요소가 될 것이다.

📍 ref의 장점은?

ref를 콘솔로 찍어보면(console.log(maneInput)) 객체가 출력된다

{current: input#username}

ref는 항상 객체이며 항상 current 프롭을 갖고있다. current 프롭은 그 ref가 연결된 실제 값을 갖는다. 모든 키 입력을 기록하지 않아도 요소에 저장된 값에 접근할 수 있다는 뜻이다. state필요 없이! 제출 버튼이 눌렸을 때 읽을 수 있다. (changeHandler들이나 input의 value, onChange등을 전부 삭제해 간결한 코드가 된다)
아래처럼 상수를 만들어 값을 저장할 수 있다.

const enteredName = nameInputRef.current.value

값만 읽고싶다면 ref가 좋을것이다. 더 간결. 편하게 요소에 접근 등 많은 기능이 있다.
state는 더 깨끗한 코드를 만들지만 코드의 양이 길어진다.

📍 제어되지 않는(uncontrolled) 컴포넌트와 제어되는(controlled) 컴포넌트

1️⃣ useRef

ref로 값에 접근하는 경우라면, input의 경우는 제어되지 않는 컴포넌트라고 할 수 있다. 왜냐면 input은 내장 state이기 때문에 이것들 안으로 반영되는 값은 리액트에 의해 제어되지 않는다. 기본적인 인풋 작동법을 따르고 있다. 인풋에서 데이터를 가져오지만 다시 보내진 않는다.
input 컴포넌트에 대해 이야기할 때는(일반적으로 form), 브라우저에 의해 내부 state를 갖는 경향이 있어 사용자의 입력을 받아 저장하고 반영하는 인풋 요소가 구성되어있다고 한다. 그리고 리액트 앱에서 해당 컴포넌트로 작업할 때 우리는 리액트 state를 그것과 연결하려한다.

2️⃣ useState

ref 말고 이전에는 state를 관리하고(useState), 모든 키 입력에서 해당 state를 업데이트했다. 그리고 그 값 프롭으로 그 state를 인풋에 다시 공급한다. 이것이 제어된 접근 방식이다. 내부 state가 리액트에 의해 제어되기 떄문이다.

[더 나아가서, 실전 예시] 사용자 지정 컴포넌트에서 ref 프롭을 바로 사용하기

🚀 폼이 제출되면 장바구니에 해당 항목을 추가하는 로직

//src>components>products>ProductItemAddForm.js

import { useRef, useState } from "react";
import Input from "../Input";
import styled from "styled-components";

const ProductItemAddForm = (props) => {
  const [amountIsValid, setAmountIsValid] = useState(true);const amountInputRef = useRef();

  // [이벤트 객체를 얻을 submitHandler]
  const submitHandler = (event) => {
    event.preventDefault(); //페이지를 다시 로드하지 않게

    // -> ref에 저장된 값을 가져옴
    // useRef로 생성된 ref에 대해서는 항상 .current.value를 써야함
    // amountInputRef.current는 ref에 저장된 인풋 요소를 가리킴
    // 모든 인풋 요소는 기본적으로 value속성을 가짐 = 현재입력된 값을 갖고있는 곳 =문자열
    const enteredAmount = ✅amountInputRef.current.value;
    const enteredAmountNumber = +enteredAmount; //문자열->숫자

    //공백을 지우고, 값이 없다면 등등 유효성 검사
    if (
      enteredAmount.trim().length === 0 ||
      enteredAmountNumber < 1 ||
      enteredAmountNumber > 10
    ) {
      // 위에서 설정한 조건이 유효하지 않은 값이라면 submitHandler 함수를 더이상 실행시키지 않고 반환, 그리고 오류 메세지 반환
      setAmountIsValid(false); //유효하지않음!으로 바꿔줌
      return;
    }

    // 추가하려는 아이템은 입력 수량 외에도 더 많은 데이터 필요, 그래서 여기서 context메소드를 호출하지 않고 프롭으로 얻어올 다른 함수 호출, 여기서 입력되고 검증된 수량을 이 onAddToCart함수에 전달
    props.onAddToCart(enteredAmountNumber);
  };

  return (
    <ProductsFormContainer>
      {/* 폼이 제출되면 장바구니에 해당 항목을 추가하는 것 */}
      <form className="form" onSubmit={submitHandler}><Input
          // 사용자 지정 컴포넌트에서 ref 프롭을 바로 사용하기(원래는 안돰), ref를 받고싶은 컴포넌트(Input컴포넌트)로 가서 컴포넌트 함수를 React.forwardRef로 감싸고 매개변수 추가. 그러면 Input 컴포넌트는 forwardRef의 인수가 되는 것이고, 그럼 ref를 얻을 수 있다(ref prop을 통해 컴포넌트에 설정할 수 있다)
          // 결국 ref를 통해 input에 접근할 수 있다 =submitHandler에 입력된 값을 읽을수있다
          ✅ref={amountInputRef}
          label="수량"
          input={{
            id: "amount_" + props.id,
            type: "number",
            min: "1",
            max: "100",
            step: "1",
            defaultValue: "1",
          }}
        />
        <button>+ CART</button>
        {/* 유효하지않은 값 에러메세지 출력 */}
        {!amountIsValid && <p>1~10개의 상품 가능!</p>}
      </form>
    </ProductsFormContainer>
  );
};

🚀 Input 컴포넌트에서 설정해줄 것이 있다


const Input = ✅ React.forwardRef((props, ref) => {
  return (
    <InputContainer>
      <label htmlFor={props.input.id}>{props.label}</label>
      {/* props.input에서 얻은 키-값 쌍 {type:'text}을 스프레드 연산자는 type='text로 추가된다*/}

      {/* forwardRef로 감쌌기때문에 이제 Input의 input에 ref를 전달할 수 있다 */}
      <input ✅ref={ref} {...props.input} />
      <div></div>
    </InputContainer>
  );
});

export default Input;
profile
👩🏻‍💻

0개의 댓글