리액트 기본 배우기 -폼에 대해-

1. 폼이란 무엇인가

 

우리말로 양식

 

 

보통은 회원가입을 하거나, 로그인을 할 때 위와 같이 텍스트를 입력하는 양식을 많이 볼 수 있다

 

텍스트 입력 뿐만 아니라 체크박스, select 등 사용자가 무언가 선택을 하는 것 모두 폼이라 생각하면 된다

 

폼은 사용자로부터 입력을 받기 위해 사용하는 것

 

 

2. 리액트와 HTML의 폼

 

리액트와 HTML에서 폼은 조금 차이가 있다

 

리액트는 컴포넌트 내부에서 state를 통해 데이터를 관리

 

HTML 폼은 엘리먼트 내부에 각각의 state가 존재함

 

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

 

위 코드는 기본적인 HTML 폼

 

사용자의 이름을 입력받고 제출하는 간단한 코드

 

리액트에서도 잘 동작하나, 자바스크립트 코드를 통해 사용자가 입력한 값에 접근하기에는 불편한 구조

 

리액트가 자바스크립트 기반이라 자바스크립트 코드에서 사용자가 입력한 값에 접근하고 제어할 수 있어야 웹페이지를 개발할 때 더 편리하다

 

 

3. 제어 컴포넌트

 

제어 컴포넌트는 사용자가 입력한 값에 접근하고 제어할 수 있도록 해주는 컴포넌트

 

이름 그대로 누군가의 통제를 받는 컴포넌트

 

그 값이 리액트의 통제를 받는 입력 폼 엘리먼트 (input form element)

 

아래 그림의 왼쪽은 HTML 폼을 나타내며, 오른쪽은 제어 컴포넌트를 나타낸다.

 

HTML 폼에서는 각 엘리먼트가 자체적으로 state를 관리

 

<input>, <textarea>, <select>가 각각 내부에 state를 가지고 있다

 

 

하지만 른쪽 제어 컴포넌트는 모든 데이터를 state에서 관리

 

앞에서 배운 것처럼 state의 값을 변경할 때는 무조건 setState() 함수를 사용

 

위 그림은 클래스 컴포넌트 기준인데, 함수 컴포넌트에서는 useState() 훅으로 state 관리

 

이처럼 제어 컴포넌트는 리액트에서 모든 데이터를 통제할 수 있는 구조

 

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

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

    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>
    )
}

 

 

위 코드에서 <input> 태그의 value={value}부분을 볼 수 있는데

 

리액트 컴포넌트의 state에서 값을 가져다가 value에 넣어주는 것이다.

 

그래서 항상 state에 들어있는 값이 input에 표시된다.

 

또한 입력값이 변경되었을 때 호출되는 부분이 onChange인데, handleChange함수가 호출되도록 만들었다.

 

handleChange 함수에서는 setValue() 함수를 사용해서 새롭게 변경된 값을 value라는 이름의 state에 저장

 

참고로 onChange 콜백 함수의 첫번째 파라미터인 event는 이벤트 객체

 

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

 

그리고 event.target은 현재 발생한 이벤트의 타겟

 

event.target.value는 해당 타겟의 value 속성값

 

여기서 target은 변경대상 input엘리먼트이고, event.target.value는 변경대상 input엘리먼트의 값

 

이처럼 제어 컴포넌트를 사용하면 입력값이 리액트 컴포넌트의 state를 통해 관리

 

여러개의 입력 양식값을 원하는 대로 조종할 수 있다는 말

 

입력 양식의 초기값을 내가 원하는 대로 넣어줄 수 있고,

 

다른 양식의 값이 변경되었을 때, 또 다른 양식의 값도 자동으로 변경시킬 수 있다는 것

 

다음은 사용자가 입력한 모든 알파벳을 대문자로 변경시켜서 관리하는 코드

 

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

 

 

handleChange() 함수로 들어오는 이벤트의 타겟값을 toUpperCase()함수로 모두 대문자로 변경하고,

 

그 값을 state에 저장한다.

 

이처럼 제어 컴포넌트를 사용하면 사용자의 입력값을 모두 제어할 수 있다

 

 

4. textarea 태그

 

<textarea> 태그는 여러 줄에 걸쳐 나올 정도로 긴 텍스트를 입력받기 위한 HTML 태그

 

HTML에서는 아래와 같이 텍스트를 태그가 감싸는 형태로 사용

 

<textarea> 태그의 children으로 텍스트가 들어가는 형태

 

<textarea>
    안녕하세요, 여기에 이렇게 텍스트가 들어가게 됩니다.
</textarea>

 

반면 리액트에서는 <textarea> 태그에 value라는 attribute를 사용해서 텍스트를 표시한다

 

앞에서 다룬 제어 컴포넌트 방식으로, 값을 컴포넌트의 state를 사용해서 다룰 수 있다

 

function RequestForm(props) {
    const [value, setValue] = useState('요청사항을 입력하세요.');

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

    const handleSubmit = (event) => {
        alert('입력한 요청사항: ' + value)
        event.preventDefault();
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>
                요청사항:
                <textarea value={value} onChange={handleChange}/>
            </label>
            <button type="submit">제출</button>
        </form>
    )
}

 

고객으로부터 요청사항을 입력받기 위한 requestform

 

state로는 value, 이 값을 <textarea>태그의 value라는 attribute에 넣어줘서 화면에 나타난다.

 

여기서는 value를 선언할 때 초기값 "요청사항을 입력하세요."을 넣어줘서, 처음 렌더링될때부터 <textarea>에 텍스트가 나타난다

 

 

5. select 태그

 

<select> 태그는 드롭다운 목록을 보여주는 HTML 태그

 

드롭다운 목록은 여러가지 옵션 중 하나를 선택할 수 있는 기능을 제공

 

HTML에서는 아래 코드와 같이 <option>태그를 <select>태그가 감싸는 형태로 사용

 

<select>
    <option value="apple">사과</option>
    <option value="banana">바나나</option>
    <option selected value="grape">포도</option>
    <option value="watermelon">수박</option>
</select>

 

<option> 태그에서 현재 선택된 옵션의 경우, selected라는 attribute를 가지고 있다.

 

코드를 보면 grape에 selected 속성이 들어가있는데, 현재 포도가 선택되어 있는 상태라는 뜻이다.

 

리액트에서는 <option> 태그에 selected 속성을 사용하지 않고 대신에 <select>태그에 value라는 attribute를 사용하여 값을 표시한다

 

초기값을 사용해서 selected를 구현해냄

 

function FruitSelect(props) {
    const [value,setValue] = useState('grape');

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

    const handleSubmit = (event) => {
        alert('선택한 과일: ' + value);
        event.preventDefault()
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>
                과일을 선택하세요:
                <select value={value} onChange={handleChange}>
                    <option value="apple">사과</option>
                    <option value="banana">바나나</option>
                    <option value="grape">포도</option>
                    <option value="watermelon">수박</option>
                </select>
            </label>
            <button type="submit">제출</button>
        </form>
    )
}

 

위 코드에서는 fruitselect라는 컴포넌트가 있고, 이 컴포넌트의 state로 grape라는 초기값을 가진 value가 있다.

 

이 값을 <select> 태그에 넣어주고, 값이 변경된 경우, handleChange() 함수에서 setValue()함수를 사용해서 값을 업데이트한다.

 

이 방식을 사용하면, 사용자가 옵션을 선택했을 때 value라는 하나의 값만을 업데이트하면 되기 때문에 편리하다.

 

만약 다중으로 선택이 되도록 하려면, multiple = true라는 속성을 추가해주고 value로 선택된 옵션의 값이 들어있는 배열을 넣어준다.

 

<select multiple={true} value={['B','C']}>

 

<input type="text">, <textarea>, <select> 모두 제어 컴포넌트로 만드는 방식이 비슷하다.

 

value라는 attribute를 통해 값을 전달하고, 변경할려면 onChange에서 setValue() 함수로 값을 업데이트한다.

 

이 방식은 사용자 입력을 받는 컴포넌트를 만들 때 사용하므로 잘 기억해야한다.

 

 

6. file input 태그

 

file input 태그는 디바이스의 저장 장치로부터 사용자가 하나, 또는 여러개의 파일을 선택할 수 있게 해주는 HTML 태그

 

보통 서버로 파일을 업로드하거나, 자바스크립트의 File API를 사용해서 파일을 다룰 때 사용

 

<input type="file" />

 

위 코드처럼 <input> 태그를 사용해서, 타입을 file로 해주면 된다.

 

file input 태그는 값이 읽기 전용이라 리액트에서는 비제어 컴포넌트이다.(uncontrolled component)

 

 

7. 여러개의 입력 받기

 

하나의 컴포넌트에서 여러개의 입력을 다루는 방법?

 

이런 경우에는 여러개의 state를 선언하고, 각각의 입력에 대해 사용하면 된다

 

function Reservation(props) {
    const [haveBreakfast, setHaveBreakfast] = useState(true)
    const [numberOfGuest, setNumberOfGuest] = useState(2)

    const handleSubmit = (event) =>{
        alert(`아침식사 여부: ${haveBreakfast}, 방문객 수: ${numberOfGuest}`)
        event.preventDefault()
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>
                아침식사 여부:
                <input
                type="checkbox"
                checked={haveBreakfast}
                onChange={(event) =>{
                    setHaveBreakfast(event.target.checked)
                }}/>
            </label>
            <br />
            <label>
                방문객 수:
                <input
                type="number"
                value={numberOfGuest}
                onChange={(event)=>{
                    setNumberOfGuest(event.target.value)
                }}/>
            </label>
            <button type="submit">제출</button>
        </form>
    )
}

 

 

위 코드는 resevation이라는 이름을 가진 호텔 예약을 위한 컴포넌트

 

필요한 정보 두가지를 입력받도록 되어 있다.

 

하나는 아침식사 선택 여부, 다른 하나는 방문객 수

 

아침식사 선택 여부 havebreakfast를 받기 위한 <input> 태그는 타입이 checkbox

 

값이 변경되면 setHaveBreakfast()함수를 통해 값을 업데이트

 

방문객 수를 입력받기 위한 <input> 태그는 type이 number로 되어 값이 변경되면 setNumberOfGuest()함수를 통해 값을 업데이트

 

클래스 컴포넌트에서는 setState() 함수 하나로 모든 state의 값을 업데이트했으나,

 

함수 컴포넌트에서는 각 state의 변수마다 set 함수가 따로 존재해서, 위와 같이 각각의 set함수로 구현한다

 

 

8. input null value

 

제어 컴포넌트에 value prop을 정해진 값으로 넣으면, 코드를 수정하지 않는 한 입력값을 바꿀 수 없다.

 

value prop은 넣되, 자유롭게 입력할 수 있게 만들고 싶다면 값에 undefined나 null을 넣어준다.

 

ReactDOM.render(<input value="hi" />, rootNode)

setTimeout(function() {
    ReactDOM.render(<input value={null}/>, rootNode)
},1000)

 

처음에 <input>의 값이 hi로 정해져 있어서, 값을 바꿀 수 없는 입력 불가 상태

 

timer에 의해 1초 뒤에 value가 null인 <input> 태그가 렌더리오디면서 입력 가능한 상태로 바뀜

 

이런 방법으로 value prop을 넣으면서 동시에 사용자가 입력을 자유롭게 할 수 있게 만들 수 있다

 

 

9. 실습으로 따라하기

 

$npx create-react-app my-app으로 리액트 프로젝트 하나 생성

 

src 폴더내에 chapter_11이라는 이름의 폴더 하나 생성

 

그리고 SignUp.jsx 파일 만들고 코드 작성

 

import React,{useState} from "react";

function SignUp(props) {
    const [name,setName] = useState("");

    const handleChangeName = (event) => {
        setName(event.target.value)
    }

    const handleSubmit = (event) => {
        alert(`이름: ${name}`)
        event.preventDefault()
    }

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

export default SignUp

 

SignUp 컴포넌트는 이름을 입력할 수 있는 <input>태그와 입력된 값을 저장하기 위한 name이라는 state를 갖고 있다.

 

이제 만든 컴포넌트를 화면에 렌더링하기 위해 index.js파일을 수정

 

import SignUp from './chapter_11/SignUp'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <SignUp />
  </React.StrictMode>
);

 

npm start로 실제 실행

 

 

실제 값을 입력해보고 제출하면...

 

 

이번엔 signup 컴포넌트에 성별도 입력받을 수 있도록 다음과 같이 수정

 

import React,{useState} from "react";

function SignUp(props) {
    const [name,setName] = useState("");
    const [gender, setGender] = useState("남자")

    const handleChangeName = (event) => {
        setName(event.target.value)
    }

    const handleSubmit = (event) => {
        alert(`이름: ${name}, 성별: ${gender}`)
        event.preventDefault()
    }

    const handleChangeGender = (event) => {
        setGender(event.target.value)
    }

    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>
            <button type="submit">제출</button>
        </form>
    )
}

export default SignUp

 

 

gender라는 이름의 state가 추가되었고 성별을 입력받기 위한 <select> 태그가 추가

 

<select> 태그에는 총 2가지 옵션이 들어가있다.

 

값이 변경되면 이를 처리하기 위해 handleChangeGender()라는 이벤트 핸들러를 만들어 사용

 

실제 실행해보면 다음과 같다.

 

 

 

TAGS.

Comments