useState
useState는 가장 기본적인 hook이며, 함수 컴포넌트에서 가변적인 상태를 가지게 한다.
const [state, setState] = useState(initialState);
함수형 업데이트
// 기존에 우리가 사용하던 방식
setState(number + 1);
// 함수형 업데이트
setState(() => {});
위 코드와 같이 setState의 ( )안에 수정할 값이 아니라 함수를 넣는 것이다. 그리고 그 함수의 인자에서 현재 state를 가져올 수 있고, { }안에서는 이 값을 변경하는 코드를 작성할 수 있습니다.
// 현재 number의 값을 가져와서 그 값에 +1을 더하여 반환한 것 입니다.
setState((currentNumber)=>{ return currentNumber + 1 });
위와 같이 수정할 수 있음.
두 방식의 차이점?
일반 사용법과 함수형 업데이트 방식의 차이점을 알아보자.
일반 업데이트 방식으로 onClick안에서 setNumber(number + 1)를 3번 호출한다.
// src/App.js
import { useState } from "react";
const App = () => {
const [number, setNumber] = useState(0);
return (
<div>
{/* 버튼을 누르면 1씩 플러스된다. */}
<div>{number}</div>
<button
onClick={() => {
setNumber(number + 1); // 첫번째 줄
setNumber(number + 1); // 두번쨰 줄
setNumber(number + 1); // 세번째 줄
}}
>
버튼
</button>
</div>
);
}
export default App;
아래 코드는 함수형 업데이트 방식이다. 이번에는 number가 3씩 증가가 된다.
// src/App.js
import { useState } from "react";
const App = () => {
const [number, setNumber] = useState(0);
return (
<div>
{/* 버튼을 누르면 3씩 플러스 된다. */}
<div>{number}</div>
<button
onClick={() => {
setNumber((previousState) => previousState + 1);
setNumber((previousState) => previousState + 1);
setNumber((previousState) => previousState + 1);
}}
>
버튼
</button>
</div>
);
}
export default App;
왜 다르게 동작할까?
일반 업데이트 방식은 버튼을 클릭했을 때 첫번째~ 세번째 줄의 있는 setNumber가 각각 실행되는 것이 아니라, 배치(batch)로 처리된다.
즉 우리가 onClick을 했을 때 setNumber라는 명령을 세번 내리지만, 리액트는 그 명령을 하나로 모아 최종적으로 한번만 실행을 시킨다. 그래서 setNumber을 3번 명령하던, 100번 명령하던 1번만 된다.
배치란 ?
불필요한 렌더링을 줄이기 위해서 state 변경함수를 batch한다.
공식문서에 따르면
리액트는 성능을 위해 setState()를 단일 업데이트(batch update)로 한꺼번에 처리할 수 있다.
불필요한 리 렌더링을 방지(렌더링 최적화)하기 위해 즉, 리액트의 성능을 위해 한꺼번에 state를 업데이트 한다.
반면에 함수형 업데이트 방식은 3번을 동시에 명령을 내리면, 그 명령을 모아 순차적으로 각각 1번씩 실행시킨다.
0에 1더하고, 그 다음 1을 1에 더하고, 2에 1을 더해서 3이라는 결과가 나온다.
여기서 마지막으로 불변성이란?
불변성은 값이나 상태를 변경할 수 없는 것이다.
원시타입 vs 참조타입
원시타입: Boolean, String, Number, null, undefined, Symbol
참조타입: Object, Array
원시타입 : 고정된 크기로 메모리에 저장, 실제 데이터가 변수에 할당
참조타입 : 데이터 크기가 정해지지 않고 메모리에 저장, 데이터의 값이 heap에 저장되며 변수에 heap 메모리 주소 값이 할당
원시타입은 불변성을 가지고 있다.
let string = 'data1'
string = 'data2'
변수 string은 data 1 > data 2로 값이 변경되었다.
하지만 위는 반은 틀리고 반은 맞았다.
예시로는 string 변수는 data 1 -> data2로 값이 변경된 것로 보이지만
하지만 실제 메모리영역에는 data1, data2 둘다 존재한다.
let string = 'data1' // 1. string: 'data1'가 메모리 영역1에 등록됩니다.
string = 'data2' // 2. string: 'data2'가 메모리 영역2에 등록됩니다.
위 예시에서는 메모리 영역을 총 2개 사용했다. 변수 string은 data1였고, 여기에 data2를 재할당했는데,
기존 메모리 영역 1에 있는 data1의 값은 그대로 두고, 메모리 영역2에 data2를 새로 할당했다.
즉, 메모리 영역에서 data2는 data1을 대체하는 것이 아니라 새로운 영역에 할당된다.
이게 불변성이다.
참조타입
let array = [1, 2, 3, 4] // 메모리영역 1
array.push(5) // 메모리영역 1
array = [1, 2, 3, 4] // 메모리영역 2 (새로운 참조값)
array.push(5)는 원본 배열을 수정하면서 불변성을 지키지 않고, array = [1, 2, 3, 4]는 원본 배열을 수정하는게 아니라 새 참조 값을 가진 새로운 배열 [1, 2, 3, 4]을 할당하여 불변성을 지켜준다.
정리하자면, 불변성의 진짜 의미는 메모리 영역에서 값이 변하지 않는다 라는 의미이다.
불변성을 지키는 이유
1. 효율적인 상태업데이트 (얕은 비교 수행)
얕은 비교란 객체의 프로퍼티를 하나하나 다 비교하지 않고, 객체의 참조 주소값만 변경되었는지 확인한다. 얕은 비교는 계산 리소스를 줄여주기 때문에 리액트는 효율적으로 상태를 업데이트 할 수 있다.
2. 사이드 이펙트 방지 및 프로그래밍 구조의 단순성.
원시타입은 애시당초 불변성 특징을 가지고 있지만 참조타입인 객체나 배열의 경우 값을 변경할 때 원본데이터가 변경될 여지가 있다. (불변성이 지켜지지 않을 수 있다). 이렇게 원본 데이터가 변경될 경우, 이 원본데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있다. 프로그래밍의 복잡도도 올라간다. 따라서 불변성을 지켜주면 사이드 이펙트를 방지하고 프로그래밍의 구조를 단순하게 유지할 수 있다.
어떻게 불변성을 지키는가?
spread operator, map, filter, slice, reduce 등등 새로운 배열을 반환하는 메소드들을 활용하면 된다.
* splice는 원본데이터를 변경함
setState를 이용할 때 원시타입 경우에는 값을 바로 넣어주어도 되지만
참조타입인 경우에는 새로운 객체나 배열을 생성한 후 값을 넣어주어야 합니다.
// 원시타입
const [number, setNumber] = useState(0)
setState(3)
// 참조타입
const [person, setPerson] = useState({ name: '', age: 30 })
setState({...person, name: 'pyo'})
정리
불변성
- 불변성이란 메모리 영역의 값을 변경할 수 없는 것이다.
- 리액트는 불변성을 지켜줌으로써 효율적인 상태업데이트를 한다.
- 리액트는 불변성을 지켜줌으로써 사이드 이펙트를 사전 방지하고 프로그래밍의 구조를 단순하게 유지한다.
- 불변성을 가진 원시타입과 달리 참조타입의 경우에는 의도적으로 불변성을 지켜주어야한다. 이 때 새로운 주소 값을 가진 객체를 생성하여 상태를 업데이트 해준다. spread operator, map, filter, slice, reduce 메소드들을 활용한다.
useState
- useState의 업데이트 방식은 2가지 방식이 있다. 각각 다르게 동작함
- useState로 원시데이터가 아닌 데이터를 변경할 때 불변성을 유지해야 한다.
'React > 리액트(코딩애플)' 카테고리의 다른 글
React Hooks - useContext(Context API) (0) | 2023.06.28 |
---|---|
React Hooks - useRef (0) | 2023.06.28 |
React Hooks - useEffect (0) | 2023.06.27 |
ajax / aixos / catch (0) | 2023.06.24 |
Lifecycle/ hook (0) | 2023.06.24 |