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을 다시 보면 아래와 같은 모습을 볼 수 있다.
뭐죠 이건?
- 합성 이벤트(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인 minutes에 event.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>
