안 좋은 방식 : 함수 호출로 리렌더링
<!DOCTYPE html>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const root = document.getElementById("root");
let counter = 0;
function countUp(){
counter = counter + 1;
render();
}
function render() {
ReactDOM.render(<Container/>, root);
}
const Container = () => (
<div>
<h3>Total clicks: {counter}</h3>
<button onClick={countUp}>Click me</button>
</div>
);
render();
</script>
</html>
버튼을 클릭할 때마다 클릭 수가 증가하는 간단한 화면을 만들었다.
vanilla js에서는 body와 span이 전부 업데이트된다.
그러나 React js에서는 딱 onClick 이벤트를 준 h3의 일부 영역만 업데이트가 된다!
<!DOCTYPE html>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const root = document.getElementById("root");
function App(){
const data = React.useState(0);
console.log(data)
return(
<div>
<h3>Total clicks: 0</h3>
<button>Click me</button>
</div>
);
}
ReactDOM.render(<App/>, root);
</script>
</html>
const data = React.useState(0);
부분이 핵심이다.
useState를 콘솔에 찍어보면 위와 같은 형태의 배열이 나온다.
여기서 data[0]은 let counter = 0;
과 같이 변수를 초기화해준 것과 같고,
data[1]은
function countUp(){
counter = counter + 1;
render();
}
위와 같이 변수의 값을 바꾸는 함수 하나를 정의한 것과 같다.
그 다음엔 useState의 요소들을 간편하게 컨트롤 할 수 있게끔 data 선언부분을
const [counter, modifier] = React.useState(0);
로 변경한다.
<!DOCTYPE html>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const root = document.getElementById("root");
function App(){
const [counter, modifier] = React.useState(0);
return(
<div>
<h3>Total clicks: 0</h3>
<button>Click me</button>
</div>
);
}
ReactDOM.render(<App/>, root);
</script>
</html>
여기에서 counter로 변수를 초기화한 건 알겠다.
그런데 왜 굳이 modifier라는 함수를 사용해서 값을 바꿔야할까?
그건 다음 강의에서!
좋은 방식 : useState()를 사용하여 값 변경된 부분만 자동으로 리렌더링
<!DOCTYPE html>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const root = document.getElementById("root");
function App(){
const [counter, setCounter] = React.useState(0);
const onClick = () => {
setCounter(counter + 1);
};
return(
<div>
<h3>Total clicks: {counter}</h3>
<button onClick={onClick}>Click me</button>
</div>
);
}
ReactDOM.render(<App/>, root);
</script>
</html>
useState()를 완전히 사용한 모습이다.
앞 수업에서 const [counter, modifier] = React.useState(0);
에서 modifier는 counter, 즉 변수의 값을 변경해주는 역할을 한다고 배웠다.
이때, 굳이 modifier 함수로 값을 변경해주는 이유는 무엇일까?
다름 아닌 리렌더링 때문이다!
3-0 예제에서는 ReactDOM.render(<App/>, root);
을 함수로 만들어서 일일이 호출해주며 리렌더링을 했다.
그러나 여기서는 modifier, 즉 setCounter가 변수가 변경되는 부분만 알아서 리렌더링을 해준다.
const root = document.getElementById("root");
function App(){
const [counter, setCounter] = React.useState(0);
const onClick = () => {
// setCounter(counter + 1);
setCounter((current) => current + 1);
};
return(
<div>
<h3>Total clicks: {counter}</h3>
<button onClick={onClick}>Click me</button>
</div>
);
}
ReactDOM.render(<App/>, root);
위의 코드에서 변경된 부분은 modifier에 해당하는 부분이다.
setCounter(counter + 1);
이렇게 단순히 함수 안에 매개변수로 직접 넣어주는 방식에서
setCounter((current) => current + 1);
current라는 변수를 선언한 뒤에 수식이 들어갔다.
이 방법은 counter 변수의 변형을 예방하고, 다음 state의 값이 무조건 현재 값을 바탕으로 나오게 된다.
function App(){
return(
<div>
<h1 className="hi">Super Converter</h1>
<label htmlFor="minutes">Minutes</label>
<input id="minutes" placeholder="Minutes" type="number"></input>
</div>
);
}
보다시피 class는 className으로, for는 htmlFor라고 적어야 무사히 돌아간다.
function App(){
const [minutes, setMinutes] = React.useState();
const onChange = (event) => {
setMinutes(event.target.value);
};
return(
<div>
<h1 className="hi">Super Converter</h1>
<label htmlFor="minutes">Minutes</label>
<input value={minutes} id="minutes" placeholder="Minutes"
type="number" onChange={onChange}></input>
<h4>You want convert {minutes}</h4>
</div>
);
}
위와 같이 코드를 작성하면 input 칸안에 적은 숫자를 인터렉티브하게 띄울 수 있게 된다.
리액트에서 이와 같이 value값을 컨트롤하려면 state를 사용해야한다.
const [minutes, setMinutes] = React.useState();
const onChange = (event) => {
setMinutes(event.target.value);
};
위의 경우처럼 먼저 useState 함수를 정의해준 후 변화가 생길 때마다 리렌더링 할 수 있도록 onChange 이벤트를 정의한다.
function App(){
const [minutes, setMinutes] = React.useState();
const onChange = (event) => {
setMinutes(event.target.value);
};
const reset = () => {
setMinutes(0);
}
return(
<div>
<h1 className="hi">Super Converter</h1>
<div>
<label htmlFor="minutes">Minutes</label>
<input value={minutes} id="minutes" placeholder="Minutes" type="number" onChange={onChange}></input>
</div>
<div>
<label htmlFor="hours">Hours</label>
<input value={Math.round(minutes / 60)} id="hours" placeholder="Hours" type="number"></input>
</div>
<button onClick={reset}>Reset</button>
</div>
);
}
해당 프로그램은 분을 시로 변환해주는 프로그램이다.
state를 사용해 minutes에 /60 을 줘서 간단하게 만들었다.
또한, reset 함수도 state를 통해 간단하게 구현할 수 있었다.
<!DOCTYPE html>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const root = document.getElementById("root");
function App(){
const [amount, setAmount] = React.useState();
const [flipped, setFlipped] = React.useState(false);
const onChange = (event) => {
setAmount(event.target.value);
};
const reset = () => setAmount(0);
const onFlip = () => {
reset();
setFlipped((current) => !current);
};
return(
<div>
<h1 className="hi">Super Converter</h1>
<div>
<label htmlFor="minutes">Minutes</label>
<input value={flipped ? amount * 60 : amount} id="minutes" placeholder="Minutes" type="number" onChange={onChange} disabled={flipped}></input>
</div>
<div>
<label htmlFor="hours">Hours</label>
<input value={flipped ? amount : Math.round(amount / 60)} id="hours" placeholder="Hours" type="number" disabled={!flipped} onChange={onChange}></input>
</div>
<button onClick={reset}>Reset</button>
<button onClick={onFlip}>Filp</button>
</div>
);
}
ReactDOM.render(<App/>, root);
</script>
</html>
Flip이라는 버튼을 눌렀을 때 시간, 분 변환을 바꿀 수 있으며 결과값이 나오는 부분은 disabled 속성이 적용되도록 기능을 추가했다.
amount와 flipped 두 개의 state를 사용한다.
amount는 input의 value를 컨트롤 하는 데에 사용되고, flipped는 true/false 값을 부여하여 input의 disabled 속성을 컨트롤 하는 데에 사용된다.
value={flipped ? amount * 60 : amount}
value={flipped ? amount : Math.round(amount / 60)}
value 값을 컨트롤하는 부분에서는 삼항연산자가 사용되었는데, 두 가지의 state를 혼합하여 사용하여 state를 좀 더 적극적으로 활용한 부분이라 할 수 있다.
<!DOCTYPE html>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const root = document.getElementById("root");
function App(){
const [index, setIndex] = React.useState("x");
const onSelect = (event) => {
setIndex(event.target.value);
};
return(
<div>
<h1 className="hi">Super Converter</h1>
<select value={index} onChange={onSelect}>
<option value="x">select your units</option>
<option value="0">Minutes & Hours</option>
<option value="1">Km & Miles</option>
</select>
<hr/>
{index === "x" ? "Please select your units" : null}
{index === "0" ? <MinutesToHours/> : null}
{index === "1" ? <KmToMiles/> : null}
</div>
);
}
function KmToMiles(){
const [amount, setAmount] = React.useState();
const [flipped, setFlipped] = React.useState(false);
const onChange = (event) => {
setAmount(event.target.value);
};
const reset = () => setAmount(0);
const onFlip = () => {
reset();
setFlipped((current) => !current);
};
return(
<div>
<div>
<label htmlFor="kilometers">Kilometers</label>
<input value={flipped ? Math.round(amount / 0.6214) : amount} id="kilometers" placeholder="Kilometers" type="number" onChange={onChange} disabled={flipped}></input>
</div>
<div>
<label htmlFor="miles">Miles</label>
<input value={flipped ? amount : Math.round(amount * 0.6214)} id="miles" placeholder="Miles" type="number" disabled={!flipped} onChange={onChange}></input>
</div>
<button onClick={reset}>Reset</button>
<button onClick={onFlip}>Filp</button>
</div>
);
}
function MinutesToHours(){
const [amount, setAmount] = React.useState();
const [flipped, setFlipped] = React.useState(false);
const onChange = (event) => {
setAmount(event.target.value);
};
const reset = () => setAmount(0);
const onFlip = () => {
reset();
setFlipped((current) => !current);
};
return(
<div>
<div>
<label htmlFor="minutes">Minutes</label>
<input value={flipped ? amount * 60 : amount} id="minutes" placeholder="Minutes" type="number" onChange={onChange} disabled={flipped}></input>
</div>
<div>
<label htmlFor="hours">Hours</label>
<input value={flipped ? amount : Math.round(amount / 60)} id="hours" placeholder="Hours" type="number" disabled={!flipped} onChange={onChange}></input>
</div>
<button onClick={reset}>Reset</button>
<button onClick={onFlip}>Filp</button>
</div>
);
}
ReactDOM.render(<App/>, root);
</script>
</html>
이번 강의는 state 챕터의 마지막 강의다.
여기서는 본격적으로 컴포넌트 단위로 화면을 구성하는 법을 익힌다.
전 강의까지는 App이라는 함수 하나로 화면을 구성했지만, 여기서 App은 KmToMiles 함수와 MinutesToHours 함수를 select 박스를 활용해 띄우는 역할만 한다.
function App(){
const [index, setIndex] = React.useState("x");
const onSelect = (event) => {
setIndex(event.target.value);
};
return(
<div>
<h1 className="hi">Super Converter</h1>
<select value={index} onChange={onSelect}>
<option value="x">select your units</option>
<option value="0">Minutes & Hours</option>
<option value="1">Km & Miles</option>
</select>
<hr/>
{index === "x" ? "Please select your units" : null}
{index === "0" ? <MinutesToHours/> : null}
{index === "1" ? <KmToMiles/> : null}
</div>
);
}
App에서 index라는 state를 추가했다.
index는 <select value={index} onChange={onSelect}>
를 통해 알 수 있듯이 select 박스의 value를 컨트롤하는 역할을 한다.
const onSelect = (event) => {
setIndex(event.target.value);
};
modifier에 해당하는 onSelect 함수 부분은 select 박스를 선택 했을 때, 해당 option value가 index값으로 바뀌게 만들고 있다.
const [index, setIndex] = React.useState("x");
이 코드를 보면 알 수 있듯이, 초기값을 x로 설정하여 첫 번째 select 박스가 Please select your units 을 띄우게 만든다.
{index === "0" ? <MinutesToHours/> : null}
{index === "1" ? <KmToMiles/> : null}
jsx 부분에서는 MinutesToHours와 KmToMiles함수를 적용한 게 보인다.
function KmToMiles(){
const [amount, setAmount] = React.useState();
const [flipped, setFlipped] = React.useState(false);
const onChange = (event) => {
setAmount(event.target.value);
};
const reset = () => setAmount(0);
const onFlip = () => {
reset();
setFlipped((current) => !current);
};
return(
<div>
<div>
<label htmlFor="kilometers">Kilometers</label>
<input value={flipped ? Math.round(amount / 0.6214) : amount} id="kilometers" placeholder="Kilometers" type="number" onChange={onChange} disabled={flipped}></input>
</div>
<div>
<label htmlFor="miles">Miles</label>
<input value={flipped ? amount : Math.round(amount * 0.6214)} id="miles" placeholder="Miles" type="number" disabled={!flipped} onChange={onChange}></input>
</div>
<button onClick={reset}>Reset</button>
<button onClick={onFlip}>Filp</button>
</div>
);
}
이 함수는 내가 직접 작성한 킬로미터와 마일 변환 프로그램 소스이다.
시간 변환 프로그램과 기본적인 형태는 똑같고, 변수명과 계산식 부분만 변경해주었다.
<input value={flipped ? Math.round(amount / 0.6214) : amount}
id="kilometers" placeholder="Kilometers" type="number"
onChange={onChange} disabled={flipped}></input>
킬로미터에서 마일로 변환하는 부분은 나누기 0.6214를 해준 뒤 math.round로 소숫점 반올림을 해주었다.
<input value={flipped ? amount : Math.round(amount * 0.6214)}
id="miles" placeholder="Miles" type="number"
disabled={!flipped} onChange={onChange}></input>
반대로 마일에서 킬로미터 변환은 0.6214를 곱해주었다.