logo
Search검색어를 포함하는 게시물들이 최신순으로 표시됩니다.
    Table of Contents
    [ReactJS] React로 영화 서비스 만들기 : 3.2. Input과 State

    이미지 보기

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

    • 22.03.26 작성

    • 읽는 데 9

    TOC

    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>
    
    profile

    FE Developer 박승훈

    노력하는 자는 즐기는 자를 이길 수 없다