저번에는 input 하나의 텍스트 입력을 제어했다.
그럼 만약에 input이 여러개라면 어떻게 해야할까?
return (
<div>
<input placeholder="이름" />
<input placeholder="닉네임" />
<button onClick={onReset}>초기화</button>
<div>
<b>값:</b>
이름 (닉네임)
</div>
</div>
)
자, 이런식으로 이름과 닉네임을 입력하는 input이 따로 존재하고, input에 텍스트를 입력하면 아래의 값에 출력된다. 초기화를 누르면 모든 input의 텍스트가 초기화되는 코드를 구현해보자.
전에는 input 이 하나여서 onReset()
을 구현할 때 {text}
를 초기화 했지만, 이번에는 {text}
의 값이 2개가 존재하므로 전과 같은 방식으로는 구현할 수 없다.
그냥 해당 text값을 다르게 지정하고 onChange 함수를 각각 따로 만들면 되지 않을까?
되기야 하지만 좀 더 효율적인 방법을 사용하는게 바람직하다.
전에 배웠던 useState를 텍스트나 숫자의 단일적인 제어가 아닌, 객체를 제어하게 된다면? 여러개의 데이터를 제어할수 있을 것이다.
그럼 useState를 객체를 제어하는 방식으로 이렇게 작성할 수 있다.
const [inputs, setInputs] = useState({ name: '', nickname:'', });
inputs라는 상태를 만들건데 초기값은 name='', nickname='' 이고 setInputs로 inputs의 값을 제어할거야 라는 뜻이다.
그리고 비구조화 할당을 이용해서
const {name, nickname} = inputs
라고 작성해서 name과 nickname 변수를 간단하게 표현한다.
더 진행하기에 앞서서 아래와 같이 코드를 작성한다음 실행해보자.
import React, {useState}from "react";
function InputSample() {
const [inputs, setInputs] = useState({
name: '',
nickname:'',
});
const {name, nickname} = inputs;
const onChange = (e) => {
console.log(e.target.name);
console.log(e.target.value);
}
const onReset = () => {
//일부러 작성하지 않음
}
return (
<div>
<input name = "name" placeholder="이름" onChange={onChange} />
<input name = "nickname" placeholder="닉네임" onChange={onChange} />
<button onClick={onReset}>초기화</button>
<div>
<b>값:</b>
이름 (닉네임)
</div>
</div>
)
}
export default InputSample;
input 창에 텍스트를 입력하면 텍스트의 내용과 input의 name값이 콘솔창에 동시 출력이 될것이다.
e.target
계속 쓰는거 귀찮으면 전에 배웠던 비구조화 할당을 이용해서
const {name, value} = e.target;
이런식으로 작성한다.
이전에 setText()
를 구현할 때는 그냥 e.target.value
를 사용해서 변화를 제어했지만, 이번에는 다수의 input이므로 그렇게 해선 안된다.
새로운 객체를 설정해서 원래있던 객체대신 그 객체의 값으로 변경한다.
자. 이렇게 글로 쓰면 모른다. 실습을 보자.
const onChange = (e) => {
const {name, value} = e.target;
const nextInputs = {
...inputs,
[name]: value, //e.target의 name과 value이다.
};
setInputs(nextInputs)
}
먼저 nextInputs
라는 객체를 정의했고, 이 객채는 위에서 usestate()
에서 만든 state
인 inputs
객체 형태를 그대로 가져온다는 뜻이다.
그리고 [name]: value
는 해당 input
값이 가지는 name(=e.target.name)
의 값에 따른 value(=e.target.value)
를 받겠다는 뜻이다.
그리고 이렇게 값이 반영된 객체를 기존에 있던 객체 대신으로 바꿔치기 되는 것이다.
setInputs(nextInputs)
요렇게.
onReset()
은 inputs
의 값을 공백으로 바꿔주면 된다.
const onReset = () => {
setInputs({
name: '',
nickname:'',
})
}
각 input 의 value 값을 지정해주는것 잊지말고 구현을 하면 최종결과는 아래와 같다.
import React, {useState}from "react";
function InputSample() {
const [inputs, setInputs] = useState({
name: '',
nickname:'',
});
const {name, nickname} = inputs;
const onChange = (e) => {
const {name, value} = e.target;
const nextInputs = {
...inputs,
[name]: value, //e.target의 name과 value이다.
};
setInputs(nextInputs)
}
const onReset = () => {
setInputs({
name: '',
nickname:'',
})
}
return (
<div>
<input name = "name"
placeholder="이름"
onChange={onChange}
value={name} />
<input name = "nickname"
placeholder="닉네임"
onChange={onChange}
value={nickname} />
<button onClick={onReset}>초기화</button>
<div>
<b>값:</b>
{name} {nickname}
</div>
</div>
)
}
export default InputSample;
※?
useState에서 setInputs 함수를 그냥 원래 있던 객체에서 값을 변경하면 더 쉬운거 아님? 근데 왜 굳이 새로운 객체를 만들어서 집어넣는 수고를 해야 하는거지?
이 의문에 대한 답은 '그대의 생각대로 하면 렌더링이 안되니까' 이다.
리액트가 컴포넌트를 렌더링하는 과정을 살펴보자
- setState를 호출 (혹은 부모로부터 props를 전달 받음)
- shouldComponentUpdate를 실행했는데 false를 리턴하면 여기서 멈추고, true를 리턴하면 다음 단계로 이동
- 가상 DOM과 실제 DOM을 비교해서 변경사항이 있으면 화면을 다시 그린다
결정적인 요인은 바로 2번이다.
shouldComponentUpdate 메서드는 기본의 state값과
새로운 값을 비교해서 동일하면 false값을 반환한다.
예를 들어보자. 다음과 같은 2개의 상태를 비교한다고 가정해보자.
const array = [1,2,3,4];
const sameArray = array;
sameArray.push(5);
console.log(array !== sameArray); // false
const array = [1,2,3,4];
const differentArray = [...array, 5];
console.log(array !== differentArray); // true
첫 번째 코드의 array와 sameArray변수가 참조하고 있는 배열의 주소값은 서로 같다. 하지만 두번째 코드의 각각의 배열은 다른 레퍼런스를 가지기 때문에 비교했을 때 다르다는 결과값이 나오게 된다.
이렇듯 한번 만든 값을 바꿀수 없는 것을 불변성이라고 한다.
이와 같이 불변성을 유지하여 코드를 작성하면 각 객체의 값이 아닌 레퍼런스 값만 비교를 해주면 된다.
따라서 내가 만약 usestate 에 정의한 stateinputs
를 setInputs
에서 정의할때 inputs
를 가져와서 값만 변경한다고 했다면, 같은 레퍼런스를 가지기 때문에 shouldComponentUpdate
에서 false값이 나오고, 렌더링이 되지 않는 것이다.