[React] 기본 지식 정리 2편

Muru·2024년 11월 19일

[React] 지식 저장소

목록 보기
2/30
post-thumbnail

6 : state와 props의 차이

State컴포넌트 내부 상태를 관리하기 위한 데이터로 컴포넌트에서 직접 변경이 가능합니다.

Props는 부모 컴포넌트가 자식 컴포넌트로 전달하는 읽기 전용 데이터 입니다.

그런데 Props가 읽기 전용으로 다룬다고 했지만 자식 컴포넌트에서 재할당 방식으로 값을 변경 할 수는 있습니다.

function Child({ name }) {
  name = "업데이트된 이름"; // 가능하지만 React 철학에 어긋남
  return <h1>{name}</h1>;
}

function App() {
  return <Child name="이름" />;
}

이런 방식의 코드는 권장하지 않는 코드입니다.

왜냐하면 자식이 Props를 수정하려고하면 부모 컴포넌트와의 데이터 흐름이 깨집니다.
더하여 React는 Props가 변경될 것이라고 기대하지 않기 때문에, 예기치 않은 동작이나 버그를 유발 할 수 있습니다.

단방향 데이터 흐름을 유지하고, 예측 가능한 동작을 보장하며, 코드의 일관성과 명확성을 지키기 위해서는 Props를 직접 변경하지 말아야 합니다.

만약 Props의 변경이 필요한경우 직접 변경하지 말고 부모로부터 받은 콜백함수를 통해 상태를 업데이트 해줄것을 요청해야합니다.

아래는 음식을 "빠스"인 초기상태의 음식을 랜덤하게 바꿔주는 버튼을 구현한 예제입니다.

App.js (부모 컴포넌트)

import React, { useState } from 'react';
import Child1 from './Child1';
const App = () => {
  const [foodName , setFoodName ] = useState("빠스")

  const foodData = ["탕수육","김피탕","팔보채"];

  const foodChange = () => {
    const food = foodData[Math.floor(Math.random() * foodData.length)];
    setFoodName(food);
  }

  return (
    <Child1 foodName={foodName} foodChange={foodChange}/>
  );
}
export default App;

Child1.js ( 자식 컴포넌트 )

import React from 'react';

function Child1({foodName, foodChange}) {
  return (
    <div>
      <h1>{foodName}</h1>
      <button onClick={foodChange}>음 식 바 꾸 기</button>
    </div>
  );
}

export default Child1;

7 : key props를 사용하는 이유

배열 또는 리스트를 사용 할 때 key props를 명시적으로 사용하지 않을 경우 다음과같은 창이 나옵니다.
Warning: Each child in a list should have a unique "key" prop.

그렇다고 index를 사용하는것은 권장하지 않는 방법입니다. 요소가 추가/삭제/수정이 될경우 어떤 순서로 출력될지 보장하지 못하기 떄문입니다.


App.js (출력은 되지만 추후 요소가 변동될경우 어떻게 출력될지 장담하지 못한다.)

const items = [
    { name: "첫번째 주자", age: 25 },
    { name: "두번째 주자", age: 26  },
    { name: "세번째 주자", age: 27  },
]
  return (
    <div>
      <ul>
        {items.map((item) => (
          <li>
            <span> {item.name}</span>
            <span> {item.age}</span>
          </li>
        ))}
      </ul>
    </div>
  );

따라서 리액트에서 사용 할 수 있는 key props를 요소마다 명시적으로 부여하여 예측 가능하게끔 만들어야 합니다. 보통 key 값으로 각 요소의 고유 ID를 부여하는게 좋습니다.

const items = [
    {id :1, name: "첫번째 주자", age: 25 },
    {id :2, name: "두번째 주자", age: 26  },
    {id:3, name: "세번째 주자", age: 27  },
]
  return (
    <div>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            <span> {item.name}</span>
            <span> {item.age}</span>
          </li>
        ))}
      </ul>
    </div>
  );

8. 제어 컴포넌트 vs 비제어 컴포넌트

제어 컴포넌트 : 폼 요소에서 React의 상태(state)를 사용하여 데이터를 관리합니다.

비제어 컴포넌트 : 폼 요소에서 React에 의해 상태를 사용하지 않고, DOM을 조작하여 데이터를 관리합니다.

일반적으로 <input>, <textarea>, <select> 같은 입력 폼을 사용자가 입력할때 React의 State에 의해서 값이 제어되기를 원한다면 비제어 컴포넌트는 부적절합니다.
예를들어 로그인 폼을 만들때 복잡한 실시간 유효성 검사를 해야한다면 어떨까요?
물론 비제어 컴포넌트(ref)로도 구현은 가능합니다. 하지만 굳이 그럴 필요가 없습니다. 코드가 비제어 컴포넌트를 사용했을때보다 훨씬 간결해집니다. 리액트 공식문서에서도 웬만하면 제어 컴포넌트를 사용하라고 권장합니다. 그게 React의 흐름에도 맞으니까요.

간단한 로그인 폼을 구현해보겠습니다.

function App() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  // 폼 제출
  const handleSubmit = (event) => {
    event.preventDefault();

    if(!email.includes("@")) {
      setError("유효하지 않은 이메일이에요.")
      return;
    }

    if(password.length < 6) {
      setError("유효하지 않은 비밀번호에요.");
    }

    setError("");
    alert("로그인이 되었어요.")
  }

// 이메일 입력폼이 바뀔때마다 반응!!
  const handleOnChangeEmail = (event) => {
    console.log(event.target.value);
    setEmail(event.target.value)
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email:</label>
        <input
         type="email"
         value={email}
         onChange={(event) => handleOnChangeEmail(event) }
        />
      </div>
      <div>
        <label>Password:</label>
        <input
         type="password"
         value={password}
         onChange={(event) => setPassword(event.target.value)}
        />
      </div>
      {error && <p>{error}</p>}
      <button type="submit">로그인</button>

    </form>
   
  );
}


email의 React 상태(state)를 사용하여 지속적으로 추적합니다. 이를 제어 컴포넌트를 사용한다고 할 수 있습니다. 물론 지속적으로 서버에 유효성 검사를 하는것은 비용적으로 낭비라고 볼 수 있습니다. 이를 해결하기 위해 디바운스기법을 적용하는것도 가능합니다. lodash라이브러리를 사용하여 디바운스를 적용할 수 있습니다.

  // ... 나머지는 위코드와 같고 다음코드를 추가및수정하면 됩니다.
   
  // 함수 타이머의 참조값을 유지하기 위해
  // useRef를 사용하였다.
  const debouncedHandleChangeEmail = useRef(
    debounce((value) => {
      console.log("디바운스된 이메일:", value);
    }, 1000)
  );

  const handleOnChangeEmail = (event) => {
    const value = event.target.value
    setEmail(value);
    debouncedHandleChangeEmail.current(value)
  }

그렇다고 비제어 컴포넌트가 안쓰이는것은 아닙니다.
입력 폼 양식이 간단하거나, 유효성 검사의 로직을 그다지 안쓰이거나, 폼 제출시에만 값을 사용하고자 한다면 비제어 컴포넌트가 좋은 선택이 될 수 있습니다.
다음 코드는 비제어 컴포넌트를 사용하여 폼 데이터를 관리하는 예시입니다.

import React, { useRef } from "react";
function App() {
  const inputRef = useRef(); // input 요소를 참조하기 위한 ref 생성

  const handleSubmit = (event) => {
    event.preventDefault(); // 새로고침 방지
    console.log(inputRef.current.value);
  }
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" ref={inputRef} /> {/* React 상태 사용 안 함 */}
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}
export default App;
profile
Developer

0개의 댓글