State란?
컴포넌트의 상태를 말합니다.
State는 컴포넌트 내에서 지속적으로 변경이 일어나는 값을 관리하기 위해 사용합니다.
개발자가 의도한 동작에 의해 변할 수도 있고 사용자의 입력에 따라 새로운 값으로 변경될 수도 있습니다.
State 값이 변경되고 리렌더링이 필요한 경우에 React가 자동으로 계산하여 변경된 부분을 리렌더링 합니다.
| [형식] const or let [상태를 저장할 변수명, 상태 변수 값을 변경할 Setter함수명 ] = useState(초기값); |
변수명과 Setter 함수명은 사용자가 마음대로 지정 가능합니다.
전통적인 사용방법은 대부분 [변수명, set변수명] 형태로 사용합니다.
Setter함수는 JavaScript가 내부적으로 구현해서 코드의 내용은 보여주지 않습니다.
|
const userState = useState('한사람');
console.log(typeof(userState) + " : " + Array.isArray(useState) + " : " + JSON.stringify(userState));
// 전개 연산자(spread operator)를 이용하여 두 값을 분리합니다.
let [name, setName] = userState;
console.log(name + " : " + setName);
// 콘솔창 결과 object : false : ["한사람",null] 한사람 : function () { [native code] }
|
대부분 다음과 같이 사용합니다.
|
// 전통적인 사용방법
let [age, setAge] = useState(0);
let [gender, setGender] = useState(true);
|
리액트에서 state를 변경할 때는 직접 값을 변경하지 않고, 반드시 state 변경 함수를 이용해서 변경시켜야 합니다.
직접 값을 변경시킨다면 state값은 변경 되지만, react는 변경 된것을 인지하지 못해 리 렌더링을 수행하지 못합니다.
state 변경 함수를 사용하면, 리액트는 state를 refresh하고 또한 리랜더링까지 해줍니다.
다음의 예를 보겠습니다.
State1.js 파일을 만듭니다.
|
import { Button } from "react-bootstrap";
import 'bootstrap/dist/css/bootstrap.min.css';
const { useState } = require("react")
const State1 = () => {
const userState = useState('한사람');
console.log(typeof(userState) + " : " + Array.isArray(useState) + " : " + JSON.stringify(userState)); // object : ["한사람",null]
// 전개 연산자(spread operator)를 이용하여 두 값을 분리합니다.
let [name, setName] = userState;
console.log(name + " : " + setName); // 한사람 : function () { [native code] }
// 전통적인 사용방법
let [age, setAge] = useState(0);
let [gender, setGender] = useState(true);
// 나이를 앞에 0을 붙여 두자리 문자열로 만듬
let ageString = `${age}`.padStart(2, 0);
// 리렌더링을 확인하기 위해 로그 출력
console.log(`그려짐 : ${name}(${age}세, ${gender ? "남":"여"})`);
const clickHandler = (e)=>{
switch(e.target.id){
case 'btn1':
// 직접 값 변경 : 리렌더링되지 않음
name= '두사람';
age= 18;
gender = false;
console.log(`클릭 : ${name}(${age}세, ${gender ? "남":"여"})`);
break;
case 'btn2':
// Setter 함수로 값 변경 : 리렌더링됨
setName('두인간');
setAge(21);
setGender(false);
break;
default: // 생략하면 터미널에 경고 발생
break;
}
}
return(
<div>
{name}({ageString}세, {gender?"남자":"여자"})
<br/>
<Button variant="outline-success" onClick={clickHandler} size="sm" id='btn1'>직접 값 변경</Button>
{` `}
<Button variant="outline-success" onClick={clickHandler} size="sm" id='btn2'>Setter로 값 변경</Button>
</div>
)
}
export default State1;
|
App.js 파일을 만듭니다.
|
import State1 from "./State1";
const App= () =>{
const style = {textAlign:"center", width:"300px", padding:"10px", margin:"10px"}
return (
<div style={style}>
<State1/>
</div>
);
}
export default App;
|
실행 화면 입니다.
직접 값을 변경하면 변수의 값은 변경되었지만 리렌더링이 일어나지 않아 화면의 값은 변화가 없습니다.

Setter 함수로 값을 변경하면 리렌더링이 일어나 화면의 값에 바로 적용이 됩니다.
콘솔창을 확인해 보면 리렌더링이 확인 됩니다.

setter를 이용할 때 두가지 방법이 있습니다.
첫번째 방법을 값을 이용하여 변경하는 방법이고 두번째 방법은 함수를 사용하여 변경하는 방법입니다.
|
const [count, setCount] = useState(0);
// 값을 이용하여 변경하는 방법
setCount(count+1); // 1증가
// 함수를 이용하여 변경하는 방법
setCount((value)=>value+1); // 1증가
|
값을 이용하여 변경하는 방법은 변수명을 사용하여 새로운 값을 만들어 줍니다.
함수를 사용하여 변경하는 방법은 이전 값을 매개변수로 받고 새로운 값을 리턴해 주면 됩니다.
상태 변수 값을 계속해서 최신 값으로 유지 시키기 위해선 이 방식을 사용해야 합니다.
상태값을 변경하는 함수에 함수형 업데이트 방식을 사용하는것이 버그를 예방하고 성응을 개선하는데 도움이 됩니다.
앞으로는 이 방법을 사용하시기 바랍니다.
두가지 방법을 썼을때 차이점을 알아 보도록 하겠습니다.
리액트의 상태 변경함수는 비동기적으로 동작합니다.
setCount()함수가 호출되는 시간과 상태를 업데이트 하는 사이에 시간 지연이 있을 수 있기 때문에 변수명을 직접 사용하면 원하는 결과가 나오지 않을 수 있습니다.
|
import { useState } from "react";
import { Button } from "react-bootstrap";
import 'bootstrap/dist/css/bootstrap.min.css';
const State2 = ()=>{
const [count, setCount] = useState(0);
const clickHandler1 = ()=>{
// 값을 이용하여 변경하는 방법
setCount(count+1); // 1증가
setCount(count+1); // 1증가
// 과연 2가 증가 되었을까요? 1만 증가 됩니다.
}
const clickHandler2 = ()=>{
// 함수를 이용하여 변경하는 방법
setCount((value)=>value+1); // 1증가
setCount((value)=>value+1); // 1증가
// 과연 2가 증가 되었을까요? 2가 증가되었습니다.
}
return(
<div>
{count}
<br/>
<Button variant="outline-success" size="sm" onClick={clickHandler1}>증가1</Button>
{` `}
<Button variant="outline-success" size="sm" onClick={clickHandler2}>증가2</Button>
</div>
);
}
export default State2;
|
App.js 파일을 수정합니다.
|
import State1 from "./State1";
import State2 from "./State2";
const App = () =>{
const style = {textAlign:"center", width:"300px", padding:"10px", margin:"10px"}
return (
<div style={style}>
<State1/>
<hr/>
<State2/>
<hr/>
</div>
);
}
export default App;
|
실행 결과 입니다.
[증가1] 버튼을 누르면 두번 증가시켰는데 1만 증가 되는것을 확인 하실 수 있습니다.

[증가2] 버튼을 누르면 두번 증가싵켜 2가 증가 되는것을 확인 하실 수 있습니다.

이유는 리액트의 상태 변경함수는 비동기적으로 동작하기 때문입니다.
setCount(count+1)함수가 실행되고 값이 변경되기 전에 뒤의 setCount(count+1)함수가 또 실되었기때문에 1만 증가되는것 입니다.
setCount((value)=>value+1); 은 "(이전값)=>새로운값 " 형태입니다. 이전값에 1을 더하는것이기 때문에 항상 적용이 됩니다. 앞으로는 setter함수를 사용할 때 함수형 변경 방법을 사용하기를 권장합니다.
useState에 객체를 저장할 수 있습니다.
|
const [user, setUser] = useState({name : "한사람", age:18, gender:true});
|
객체를 저장하고 객체를 변경하는 방법에 대하여 알아보겠습니다.
State3.js 파일을 만듭니다.
|
import { Button } from "react-bootstrap";
import 'bootstrap/dist/css/bootstrap.min.css';
const { useState } = require("react")
const users = [
{name : "한사람", age:18, gender:true},
{name : "두사람", age:22, gender:false},
{name : "세사람", age:31, gender:true},
{name : "네사람", age:42, gender:true},
{name : "오사람", age:53, gender:false}
]
const State3 = ({param}) => {
const [idx, setIdx] = useState(1);
const [user, setUser] = useState(param);
const clickHandler1 = ()=>{
// 값을 이용하여 변경하는 방법
setIdx((idx+1)%users.length);
setUser(users[idx]);
}
const clickHandler2 = ()=>{
// 함수를 이용하여 변경하는 방법 : 권장
setIdx((value)=>(value+1)%users.length);
setUser(users[idx]);
}
// 전개
const {name, age, gender} = user;
// 나이를 두자리로 문자열로 변경
const ageString = `${age}`.padStart(2, 0);
return(
<div style={{marginBottom:"5px"}}>
{name}({ageString}세, {gender?"남자":"여자"})
<br/>
<Button variant="outline-success" size="sm" onClick={clickHandler1}>변경1</Button>
{` `}
<Button variant="outline-success" size="sm" onClick={clickHandler2}>변경2</Button>
{` `}
<Button variant="outline-success" size="sm" onClick={()=>{
setIdx((value)=>(value+1)%users.length);
setUser(users[idx]);
}}>변경3</Button>
</div>
)
}
export default State3;
|
App.js 파일을 수정합니다.
|
import State1 from "./State1";
import State2 from "./State2";
import State3 from "./State3"; const App = () =>{
const style = {textAlign:"center", width:"300px", padding:"10px", margin:"10px"}
return (
<div style={style}>
<State1/>
<hr/>
<State2/>
<hr/>
<State3 param={ {name : "한사람", age:18, gender:true} }/> {/* 객체 전달 방법 */}
<hr/>
</div> );
}
export default App;
|
실행 결과 입니다.

click 이벤트를 함수로 만들어 변경하는 방법과 익명 함수를 전달해서 이벤트 처리 하는 방법을 알아 보았습니다.
|
객체를 속성으로 전달하는 방법
<State3 param={ {name : "한사람", age:18, gender:true} }/> click 이벤트를 함수로 만들어 변경 <Button variant="outline-success" size="sm" onClick={clickHandler2}>변경2</Button> 다음은 버튼 사이에 공백 하나를 넣기 위해 사용함
{` `}
익명 함수를 전달해서 이벤트 처리 하는 방법 <Button variant="outline-success" size="sm" onClick={()=>{
setIdx((value)=>(value+1)%users.length);
setUser(users[idx]);
}}>변경3</Button>
|
배열이나 객체를 State로 사용시 값을 잘못 처리하여 리렌더링이 않되는 경우를 예로 만들어 보았습니다.
잘못된 방법과 올바른 방법을 비교해 보시기 바랍니다.
State4.js 파일을 만듭니다.
|
import { useState } from "react";
import { Button } from "react-bootstrap";
import 'bootstrap/dist/css/bootstrap.min.css';
const State4 = ()=>{
const [array, setArray] = useState([1,2,3]);
const [object, setObject] = useState({name : "한사람", age:18, gender:true});
console.log('그려짐~~~~~\n' + JSON.stringify(object) + '\n' + array);
const clickHandler = (e) => {
switch(e.target.id){
case "btn1":
setArray((array)=>{
array[1] = 33;
console.log(array);
// 배열의 값은 변경되었지만 배열의 주소는 변경되지 않아 상태에 변화를 감지 못함
return array;
});
break;
case "btn2":
setArray((array)=>{
array[1] = 22;
// 새로운 배열로 복사해서 전달해야 함
return [...array];
});
break;
case "btn3":
setObject((object)=>{
object.age = 33;
console.log(JSON.stringify(object));
// 객체의 값은 변경되었지만 객체의 주소는 변경되지 않아 상태에 변화를 감지 못함
return object;
});
break;
case "btn4":
setObject((object)=>{
object.age = 22;
// 새로운 객체로 복사해서 전달해야 함
return {...object};
});
break;
default:
break;
}
}
return(
<div>
배열 : {
array.map((n)=>`${n}`.padStart(3, 0) + ' ')
}
<br/>
<Button variant="outline-success" size="sm" id="btn1" onClick={clickHandler}>배열 변경(잘못된 방법)</Button>
{` `}
<Button variant="outline-success" size="sm" id="btn2" onClick={clickHandler}>배열 변경(올바른 방법)</Button>
<br/>
객체 : {JSON.stringify(object)}
<br/>
<Button variant="outline-success" size="sm" id="btn3" onClick={clickHandler}>객체 변경(잘못된 방법)</Button>
{` `}
<Button variant="outline-success" size="sm" id="btn4" onClick={clickHandler}>객체 변경(올바른 방법)</Button>
</div>
);
}
export default State4;
|
App.js 파일을 수정합니다.
|
import State1 from "./State1";
import State2 from "./State2";
import State3 from "./State3"; import State4 from "./State4";
const App = () =>{
const style = {textAlign:"center", width:"300px", padding:"10px", margin:"10px"}
return (
<div style={style}>
<State1/>
<hr/>
<State2/>
<hr/>
<State3 param={ {name : "한사람", age:18, gender:true} }/> {/* 객체 전달 방법 */}
<hr/>
</div> <State4/>
);
}
export default App;
|
실행 결과 입니다.

콘솔창을 확인해보면 배열의 내용은 바뀌었지만 UI는 변경되지 않았음을 확인할 수 있습니다. 배열의 내용은 바뀌었지만 같은 배열을 사용하므로 배열이 저장된 주소에는 변화가 없습니다. 리액트가 상태의 변화를 인지하지 못해서 UI의 업데이트가 이루어지지 않습니다.

실제로 변경된 것을 확인이 가능합니다.

콘솔창을 확인해보면 객체의 내용은 바뀌었지만 UI는 변경되지 않았음을 확인할 수 있습니다. 객체의 내용은 바뀌었지만 같은 객체를 사용하므로 객체가 저장된 주소에는 변화가 없습니다. 리액트가 상태의 변화를 인지하지 못해서 UI의 업데이트가 이루어지지 않습니다.

실제로 변경된 것을 확인이 가능합니다.
'Front End > React' 카테고리의 다른 글
| [React Exam] react-router-dom 사용해보기 2 (0) | 2024.11.21 |
|---|---|
| [React Exam] react-router-dom 사용해보기 1 (2) | 2024.11.19 |
| [React Exam 40] React Hook 알아보기 (0) | 2024.11.06 |
| [React Exam 39] 가위바위보 게임 만들기 (1) | 2024.11.05 |
| [React Exam 38] 로또 번호 자동 생성기 만들기 2 (0) | 2024.11.05 |