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

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

<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" />

<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를 써야하는거죠?

  • 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 출력

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

저는 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 속성을 두어 입력하지 못하도록 하였다.

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


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상태(시 -> 분)


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

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


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

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>