091

[JS/React] 리스트와 키, 입력 폼 본문

Programming Language/JavaScript&TypeScript

[JS/React] 리스트와 키, 입력 폼

공구일 2026. 4. 18. 21:34
728x90

1. 리스트와 키

 

- 리스트(List)란 같은 종류의 아이템을 순서대로 모아 놓은 것으로, 자바스크립트에서는 배열로 표현합니다.

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map(number =>
  <li>{number}</li>
);

-> Array.map()을 사용할 때 key 없이 사용하면 위처럼 "Each child in a list should have a unique key prop"라는 경고가 뜹니다. 렌더링은 되지만 성능 최적화가 안된다는 의미입니다.

 

- 키(key)가 필요한 이유는 React는 Virtual DOM 비교 시 리스트 아이템이 변경/추가/삭제되었는지 파악해야합니다. key가 없으면 React는 전체 리스트를 다시 렌더링 하고, key가 있으면 변경된 항목만 업데이트합니다.(성능 최적화) 키의 유효범위는 같은 리스트 안에서만 고유하면 됩니다.

- 키 값 설정하는 법
(1) 배열의 내부 값인 숫자/문자 값을 키로 설정하기: 배열에 중복값이 있을 수 있어 권장하지 않습니다.

numbers.map(number =>
  <li key={number.toString()}>
    {number}
  </li>
);

(2) 고유 id를 키로 설정하기(권장)

const todos = [
  { id: 1, text: '첫 번째 할일' },
  { id: 2, text: '두 번째 할일' },
];
todos.map(todo =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

(3) 인덱스를 키로 설정하기: 아이템 순서가 바뀌거나 추가/삭제가 있어나면 버그가 발생할 수 있으므로 id가 없을 때만 사용합니다. 

todos.map((todo, index) =>
  <li key={index}>
    {todo.text}
  </li>
);

 

 

2. 폼(forms)

 

- 폼(Forms)은 사용자로부터 입력을 받기 위한 UI요소로, HTML 기본 폼과 React 폼은 작동방식이 다릅니다.

(1)input  차이

<form>
  <label>
    이름:
    <input type="text" name="name" />
  </label>
  <button type="submit">제출</button>
</form>

-> HTML 폼은 각 엘리먼트(input, textarea, select)가 내부적으로 자체 state를 가지며, 이를 DOM이 관리합니다.

const [value, setValue] = useState('');

<input
  value={value}
  onChange={(e) => setValue(e.target.value)}
/>

-> 컴포넌트의 state가 모든 입력 값을 관리하며, React가 데이터의 단일 진실 공급원(single source of truth)이 됩니다.


(2) textarea: 사용자가 여러 줄의 텍스트를 입력할 수 있는 텍스트 입력 영역을 정의할 때 사용합니다.(출저:https://www.tcpschool.com/html-tags/textarea)

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

<textarea>
  내용이 children으로 들어감
</textarea>
<textarea
  value={value}
  onChange={handleChange}
/>

-> React에서 textarea는 value 속성을 사용해 단일 태그로 사용할 수 있어 input과 사용방식이 통일됩니다.


(3) select

<select>
  <option value="apple">사과</option>
  <option selected value="banana">바나나</option>
</select>
const [value, setValue] = useState('grape');

<select
  value={value}
  onChange={(e) => setValue(e.target.value)}
>
  <option value="apple">사과</option>
  <option value="banana">바나나</option>
  <option value="grape">포도</option>
</select>

-> React는 select 태그의 selected 속성 대신 최상위 select에 value 속성을 사용합니다. input, textarea, select 모두 value + onChange 패턴으로 통일됩니다.

 

- React에서 input, textarea, select를 제어 컴포넌트(controlled component)로 사용한다. 사용자가 입력할 때마다 onChange 이벤트로 state를 업데이트하고, 그 state가 다시 Input의 value로 연결됩니다. 

-> 제어 컴포넌트를 통해 입력 초기값을 설정하거나 입력을 강제변환(소문자->대문자), 한 입력값 변경 시 다른 필드 값이 자동으로 변경되거나 유효성을 검사하거나 제출 버튼 활성/비활성 제어 등이 가능합니다. 

 

-> event.target은 이벤트가 발생한 DOM 엘리먼트를 가리킵니다. event.target.value는 input의 혐재 입력값, event.target.checked는 checkbox의 체크여부를 의미합니다. event.preventDefault()는 form submit나 a같은 페이지 이동을 하는 태그를 사용할 때 이동을 차단하기 위해 꼭 작성해줘야합니다. 

 

+) 입력 값 무조건 대문자로

import React, { useState } from "react";

export default function NameForm() {
  const [value, setValue] = useState('');

  const handleChange = (event) => {
    //setValue(event.target.value);
    setValue(event.target.value.toUpperCase());
  };

  const handleSubmit = (event) => {
    alert(`입력한 이름: ${value}`);
    event.preventDefault(); // 페이지 새로고침 방지
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        이름:
        <input
          type="text"
          value={value}
          onChange={handleChange}
        />
      </label>
      <button type="submit">제출</button>
    </form>
  );
}

 

- 비제어 컴포넌트(uncontrolled component)는 ref를 사용해 DOM에서 직접 값을 읽는 방식을 말하며, React에서는 state로 모든 것을 관리하는 제어 컴포트를 권장하지만, 파일 업로드(input type="file")는 읽기 전용이라 항상 비제어 방식을 써야합니다.

 

 

+) 회원가입 컴포넌트

import React, { useState } from "react";
export default function SignUp(props) {
    const [name, setName] = useState("");
    const [gender, setGender] = useState("남자");
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [birthdate, setBirthdate] = useState("");
    const [isEssential, setIsEssential] = useState(false);
    const handleChangeName = (event) => {
        setName(event.target.value);
    };
    const handleChangeGender = (event) => {
        setGender(event.target.value);
    };
    const handleChangeEmail = (event) => {
        setEmail(event.target.value);
    };
    const handleSubmit = (event) => {
        event.preventDefault();
        if(name.trim() === ""){
            alert("이름을 입력해주세요.");
        } else if(email.trim() === ""){
            alert("이메일을 입력해주세요.");
        } else if(!isEssential){
            alert("약관에 동의해주세요.");
        } else {
            alert("가입이 완료되었습니다.");
        }
    };
    return (
        <form onSubmit={handleSubmit}>
            <label>
                이름:
                <input type="text" value={name}
                    onChange={handleChangeName} />
            </label>
            <br />
            <label>
                성별:
                <select value={gender}
                    onChange={handleChangeGender}>
                    <option value=
                        "남자">남자</option>
                    <option value=
                        "여자">여자</option>
                </select>
            </label>
            <br />
            <label>
                이메일:
                <input type="email" value={email}
                    onChange={handleChangeEmail} />
            </label>
            <br />
            <label>
                비밀번호:
                <input type="password" value={password}
                    onChange={(e)=>{setPassword(e.target.value)}} />
            </label>
            <br />
            <label>
                생년월일:
                <input type="date" value={birthdate}
                    onChange={(e)=>{setBirthdate(e.target.value)}} />
            </label>
            <br />
            <label>
                <input type="checkbox" checked={isEssential}
                    onChange={(e)=>{setIsEssential(e.target.checked)}} />
                [필수] 약관에 동의합니다
            </label>
            <br />
            <button type="submit">가입</button>
        </form>
    );
}
728x90