[ReactJS] React로 영화 서비스 만들기 : 3.2. Input과 State

작성:    

업데이트:

카테고리:

태그: , ,

JSX과 input

JSX에서는 input과 label의 for을 사용하면 안된다. 왜?

  • for은 이미 JavaScript에서 사용하고 있는 예약어이다.
  • html 문법으로 작성하면 문제가 될 수 있다.
  • html 문법이 아닌 JSX 문법으로 작성해야 한다.


기존 HTML

<h1 class="hi">Super Converter</h1>
<label for="minutes">Minutes</label>
<input id="minutes" placeholder="Miniutes" type="number" />
<label for="hours">Hours</label>
<input id="hours" placeholder="Hours" type="number" />


JSX 문법

<h1 className="hi">Super Converter</h1>
<label htmlFor="minutes">Minutes</label>
<input id="minutes" placeholder="Miniutes" type="number" />
<label htmlFor="hours">Hours</label>
<input id="hours" placeholder="Hours" type="number" />


input과 state

input이 있는데 왜 state를 써야하는거죠?

  • React JS 입장에서 이 input은 uncontrolled 요소
  • input의 value를 통제할 수 없다는 말
  • state를 이용해 value를 control하자!


function App() {
  const [minutes, setMinutes] = React.useState();
  const onChange = () => {
    console.log("wrote");
  };
  return (
    <div>
      <h1 className="hi">Super Converter</h1>
      <label htmlFor="minutes">Minutes</label>
      <input
        value={minutes}
        id="minutes"
        placeholder="Miniutes"
        type="number"
        onChange={onChange}
      />
      <label htmlFor="hours">Hours</label>
      <input id="hours" placeholder="Hours" type="number" />
    </div>
  );
}
  • onChange 함수에 의해 minutes의 value가 바뀔 때마다 함수가 호출, console에 wrote 출력


SyntheticBaseEvent

onChange 함수를 다음과 같이 바꿔보자.

const onChange = (event) => {
  console.log(event);
};
  • event 매개변수를 이용해 event에 대한 정보를 받아와 console에 출력한다.
  • 위와 같이 바꾸고 console을 다시 보면 아래와 같은 모습을 볼 수 있다.

image


뭐죠 이건?

  • 합성 이벤트(SyntheticEvent)이다.
  • ReactJS는 event를 최적화하는 과정을 거치기 때문에 가짜 이벤트를 발생
  • 이 내부의 nativeEvent가 우리가 알던 event정보
  • 이 nativeEvent 내부의 target의 value가 input 내부의 값


Then How?

아래와 같이 작성하면 input 내부의 값을 console에 출력할 수 있다.

const onChange = (event) => {
  console.log(event.target.value);
};


setState와 input value

저는 event 내부 input의 value를 화면에 띄우고 싶어요.

  • setState 함수를 이용해 event 정보로부터 state를 정의해 render해보자.
const onChange = (event) => {
  setMinutes(event.target.value);
};
  • 그러면 setMinutes의 state인 minutesevent.target.value 값 지정 후 render


  • input value는 이미 차있으므로, input value로부터 값을 받아 minutes에 저장한 후 render할 다른 컴포넌트를 만들어 확인
  • 아래는 “You want to convert ???”로 render하는 방식
<label htmlFor="minutes">Minutes</label>
<input value={minutes} id="minutes" placeholder="Miniutes" type="number" onChange={onChange}/>
<h4>You want to convert {minutes}</h4>

state를 이용해 이 input은 controlled 요소가 되었다.


분 -> 시 변환에 적용해보자.

<div>
  <label htmlFor="minutes">Minutes</label>
  <input value={minutes} id="minutes" placeholder="Miniutes" type="number" onChange={onChange}/>
</div>

<div>
  <label htmlFor="hours">Hours</label>
  <input value={minutes/60} id="hours" placeholder="Hours" type="number" disabled />
</div>
  • 변수 값을 적절히 나누어 이와 같이 작성하였다.
  • hours 부분은 disabled 속성을 두어 입력하지 못하도록 하였다.


Hours 부분

저는 분에서 시 변환 뿐만 아니라 반대로 시에서 분 변환도 하고 싶은데요?


disabled props와 Flip 버튼을 이용해 활성화-비활성화를 변경해보자.

const [flipped, setFlipped] = React.useState(false);
const onFlip = () => setFlipped((current) => !current);
return (
  <div>
    <h1 className="hi">Super Converter</h1>
    <div>
      <label htmlFor="minutes">Minutes</label>
      <input
        value={minutes}
        id="minutes"
        placeholder="Miniutes"
        type="number"
        onChange={onChange}
        disabled={!flipped}
      />
    </div>

    <div>
      <label htmlFor="hours">Hours</label>
      <input
        value={minutes / 60}
        id="hours"
        placeholder="Hours"
        type="number"
        disabled={flipped}
      />
    </div>
    <button onClick={reset}>Reset</button>
    <button onClick={onFlip}>Filp</button>
  </div>
);
  • disabled 속성은 bool 형식을 이용해 그 자체를 활성화/비활성화가 가능
  • onFlip 함수를 이용해 flipped state를 true/false를 교환
  • 이 상태에 대한 조건문을 disabled에 적용하거나 boolean형 변수를 적용해 default disabled 상태 정의
  • flipped가 최초 false에서 true가 되면 disabled 적용도 반대 상태


조건문과 삼항연산자

활성화/비활성화가 아니라 시 부분에서 값을 바꾸고 싶다니까요?

  • hours의 value에 값을 입력하면 minutes도 *60 되어서 적용되어야 한다.
  • filpped 상태에 따라 이를 적용하는 건 어떨까?
  • 삼항연산자를 활용해서 flipped 상태에 따라 공식 적용을 달리 해보자.


문법

조건 ? true일 때 : false일 때
// input#hours의 value prop

// 적용 전
value ={Math.round(amount / 60)}

// 적용 후
value={flipped ? amount : Math.round(amount / 60)}
  • 단위 변환의 혼동이 있을 수 있어 minutes와 setMinutes 부분을 amount와 setAmount로 변경
  • flipped가 true라면 amount, false라면 Math.round(amount / 60)

  • flipped가 false : 초기상태(분 -> 시)
  • flipped가 true : flip상태(시 -> 분)


select와 JSX

시분 변환 말고 다른 변환도 쓸 수는 없나요?

HTML의 select 태그를 이용해서 다른 변환을 선택해보자.


컴포넌트 분리

  • 지금까지 시분 변환을 App 컴포넌트 내에서 작성
  • 이제 App은 전체 컨테이너로 사용하고, 변환 항목마다 컴포넌트화 실시
  • MinutesToHours 컴포넌트로 분리
function App() {
  return (
    <div>
      <h1>Super Converter</h1>
      <MinutesToHours />
      <KmToMiles />
    </div>
  );
}
  • App은 단순히 컴포넌트를 담는 컨테이너의 역할
  • MinutesToHours 컴포넌트에서 h1을 제거하고 다음과 같이 쓰면 이전의 모습과 동일
  • 다른 변환 예시인 KmToMiles 컴포넌트를 만들었다고 가정


select 태그 사용

function App() {
  const [index, setIndex] = React.useState("0");
  const onSelect = (event) => {
    setIndex(event.target.value);
  };
  return (
    <div>
      <h1>Super Converter</h1>
      <select value={index} onChange={onSelect}>
        <option value="0">Minutes & Hours</option>
        <option value="1">Km & Miles</option>
      </select>
      {index === "0" ? <MinutesToHours /> : null}
      {index === "1" ? <KmToMiles /> : null}
    </div>
  );
}
  • select마다 선택사항인 option을 두어 어떤 컴포넌트 활성화할건지 선택
  • 변화가 생길 때마다(onSelect) value(index)를 받아 setIndex(setState) 함수로 전달해 index(state)에 반영
  • 삼항연산자 조건문을 이용해 컴포넌트 포함할지 말지 결정
  • JSX 내부에 그냥 글을 쓰면 HTML로 인식, 중괄호({}) 내에 쓰면 JS로 인식


전체 코드

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone@7.17.8/babel.min.js"></script>
  <script type="text/babel">
    function MinutesToHours() {
      const [amount, setAmount] = React.useState();
      const [flipped, setFlipped] = React.useState(false);
      const onChange = (event) => {
        setAmount(event.target.value);
      };
      // minutes의 값을 0으로 초기화
      const reset = () => setAmount(0);

      const onFlip = () => {
        // flip 시 값을 초기화하기 위해 reset() 함수 호출
        reset();
        // 현재 flipped state의 상태와 반대인 bool을 state에 지정
        //const onFlip = () => setFlipped(!flipped);
        // flipped state의 정말 현재 상태를 가져오기 위해 arrow 함수 사용
        setFlipped((current) => !current);
      };

      return (
        <div>
          <div>
            <label htmlFor="minutes">Minutes</label>
            <input
              value={flipped ? amount * 60 : amount}
              id="minutes"
              placeholder="Miniutes"
              type="number"
              onChange={onChange}
              disabled={flipped}
            />
          </div>

          <div>
            <label htmlFor="hours">Hours</label>
            <input
              value={flipped ? amount : Math.round(amount / 60)}
              id="hours"
              placeholder="Hours"
              type="number"
              onChange={onChange}
              disabled={!flipped}
            />
          </div>
          <button onClick={reset}>Reset</button>
          <button onClick={onFlip}>{flipped ? "Filp Back" : "Flip"}</button>
        </div>
      );
    }
    function KmToMiles() {
      return <h3>KM 2 Miles</h3>;
    }

    function App() {
      const [index, setIndex] = React.useState("0");
      const onSelect = (event) => {
        setIndex(event.target.value);
      };
      return (
        <div>
          <h1>Super Converter</h1>
          <select value={index} onChange={onSelect}>
            <option value="0">Minutes & Hours</option>
            <option value="1">Km & Miles</option>
          </select>
          <hr />
          {index === "0" ? <MinutesToHours /> : null}
          {index === "1" ? <KmToMiles /> : null}
        </div>
      );
    }

    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>
</html>

댓글남기기