1. 불변성(Immutability)이란?
데이터가 생성된 후, 그 값을 변경하지 않고 그대로 두는 것이다.
만약 데이터를 변경해야 한다면 기존 데이터는 수정하지 않고 그 데이터를 복사해서 새로운 데이터를 만든 후 수정해야 한다.
2. 기본형과 참조형 데이터
- 기본형(primitive) 데이터: 데이터를 직접 메모리에 저장
- number, string, boolean, undefined, null, symbol, bigInt
언뜻 보면 0을 5로 바꾸는 것 같지만, 0이 있는 기존 메모리 공간과 별개의 메모리 공간에서 5가 저장되고,
x는 여기서 값을 읽어오는 것이다.
기존 데이터의 수정이 없기 때문에 불변적이다.
-> 변수가 참조하는 메모리 주소가 변경된다. - 참조형 데이터: 데이터가 위치한 메모리 주소를 저장하여 값을 참조
- array, object
arr은 [1, 2, 3] 데이터를 직접 가지고 있는 것이 아니라 [1, 2, 3]이 저장돼있는 메모리 주소를 저장해 데이터를 참조하는 것이다.
만약 arr.push(4) 를 하면
arr이 주소를 통해 참고하고 있는 기존 데이터 값이 [1, 2, 3, 4]를 가르키며 변경된다.
기존 데이터가 직접 수정되기 때문에 가변적이다.
-> 변수가 참조하는 메모리 주소가 변경되지 않는다.
3. 리액트의 데이터 불변성 유지
React는 참조형 데이터(array, object)의 가변을 알아차리지 못한다.
위에서 언급했듯이, 참조형 데이터는 기존 데이터가 직접 수정이 되면서, 변수가 참조하는 메모리 주소는 변경되지 않는다.
리액트는 상태변경의 여부를 참조의 변화(메모리 주소)로 감지한다.
이때문에 일반적인 방식으로 배열과 객체의 값을 변경하면 리액트는 이를 감지하지 못해 렌더링이 일어나지 않는다.
따라서 React에선 배열과 객체와 같은 참조형 데이터는
기존 값을 복사하거나, 새로운 데이터를 만든 후 수정하도록 한다.
상태가 업데이트될 때 데이터의 불변성을 유지해야 한다는 리액트 규칙이다.
4. 배열에 항목 추가, 수정, 삭제
불변성을 지키면서 다음 users에 항목을 추가해보자.
const [users, setUSers] = useState([
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active: 'false'
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: 'false'
},
{
id: 3,
username: 'liz',
email: 'liz@example.com',
active: 'false'
}
]);
const nextId = useRef(4);
항목 추가
- spread 연산자
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers([...users, user])
setInputs({
usetname: '',
email: '',
};
nextId.current += 1;
}
setUsers([...users, user])
Note that the ... spread syntax is “shallow”—it only copies things one level deep.
spread 연산자: 얕은 복사
→ 기존 데이터의 복사본을 만들어 그 복사본을 수정
- concat 함수
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
usetname: '',
email: '',
};
nextId.current += 1;
}
setUsers(users.concat(user))
concat 함수는 기존의 배열을 수정하지 않고, 새로운 원소가 추가된 새로운 배열을 만든다.
배열만 적용
항목 수정
클릭하면 username이 초록색으로 변하고, 한 번 더 클릭하면 검은색으로 변하는 것을 구현하자.
- map 함수
const onToggle = (id) => {
setUsers(
users.map(user =>
user.id === id ? {...user, active: true} : user
)
);
};
setUsers(users.map(user => user.id === id ? {...user, active: true} : user)
function User({ user, onRemove, onToggle }) {
return (
<div>
<b
style={{
cursor: 'pointer',
color: user.active ? 'green' : 'black'
}}
onClick={() => onToggle(user.id)}
>
{user.username}
</b>
<span>({user.email})</span>
<button onClick={() => onRemove(user.id)}>삭제</button>
</div>
);
}
function UserList({ users, onRemove, onToggle }) {
return (
<div>
{users.map(user => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
}
export default UserList;
항목 삭제
- filter 함수
onRemove = (id) => {
setUsers(
users.filter(user => user.id !== id)
)
};
setUsers(users.filter(user ⇒ (user.id !== id))
조건에 맞지 않는 배열은 추출해서 새로운 배열로 만든다.
객체에서 항목 삭제 역시 spread 연산자를 사용해 구현 가능하다.
function App() { // 초기 상태 객체 const [user, setUser] = useState({ name: 'Alice', age: 25, location: 'Seoul', }); // 특정 키를 불변성을 유지하며 삭제하는 함수 function handleRemoveKey(keyToRemove) { setUser(prevUser => { const { [keyToRemove]: _, ...updatedUser } = prevUser; return updatedUser; // 새로운 객체를 반환 }); }
'React' 카테고리의 다른 글
| useState의 렌더링 방식 이해하기(feat.batching) (0) | 2025.02.06 |
|---|---|
| Tanstack-Query(React-Query) (0) | 2025.02.05 |
| Redux 정리 (0) | 2025.01.21 |
| state를 reducer로 작성하기 (0) | 2025.01.20 |
| useRef와 권장사항 (1) | 2024.11.01 |