자바스크립트에서의 불변성이란?
- 원시형 (string, number, boolean 등) 데이터들은 모두 불변성을 유지한다. (값이나 상태를 변경할 수 없는 것)
let a = 1;
let b = a; //깊은 복사
b = 2
console.log(a) //1
- 원시형 데이터들은 복사가 이루어질 때, 그 값 자체를 복사해서 새로운 메모리 주소에 할당한다.
a → 0x000001 (메모리 주소) - 1 (값)
b → 0x000002 (메모리 주소) - 1 (값)
- a 변수를 b 에 복사한 이후에, b 를 수정해도 a 가 변경되지는 않는다.
- 수정된 b 자체 또한 새로운 메모리 주소에 값이 할당된다.
(b 수정 후)
a → 0x000001 (메모리 주소) - 1 (값)
0x000002 (메모리 주소) - 1 (값)
b → 0x000003 (메모리 주소) - 2 (값)
즉, 각 메모리 주소마다 할당된 값이 절대 변하지 않는 ‘불변성’을 유지하고 있다.
- 그러나 객체형 데이터들은 불변성을 유지하지 않는다.
let a = [1, 2, 3]
let b = a // 얕은 복사
b.push(4)
console.log(a)// [1,2,3,4]
- 객체형 데이터는 복사가 이루어질 때, 값을 복사하는 것이 아니라 단순히 메모리 주소를 복사하기 때문이다.
a → 0x000001 (메모리 주소) - [1, 2, 3] (값)
b → 0x000001 (메모리 주소)
- a 변수를 b 에 복사하더라도, 새로운 메모리 주소가 할당되지 않는다.
- 수정할 때 새로운 메모리 주소가 할당되지 않고, 해당 메모리 주소를 참조하고 있는 원본 a 의 값이 변경된다.
(b 수정 후)
a → 0x000001 (메모리 주소) - [1, 2, 3, 4] (값)
b → 0x000001 (메모리 주소)
즉, a 라는 데이터에 대해서 불변성이 깨졌다. (해당 메모리 주소에 할당된 값 그 자체가 변경되었다.)
정리하자면 불변성이란, 특정한 메모리 주소에 할당된 값에 대해서 값을 변경하지 않는 것을 의미한다.
리액트에서의 불변성
- 리액트는 렌더링을 할 때 얕은 비교를 수행하기 때문이다.
- 리액트는 기본적으로 state 등의 값(주소 참조값)이 변하는 것을 확인하고, 실제로 변했다면 다시 렌더링하는 특성을 가지고 있다.
- 부모 컴포넌트의 값(주소 참조값)이 변하면, 해당 컴포넌트의 자식 컴포넌트까지도 전부 다시 렌더링한다.
- 위 a 라는 변수의 배열처럼 객체의 값은 변했지만 메모리 주소는 그대로인 경우 변하지않았다고 판단, 새로운 렌더링이 안된다.
import React, { useState } from 'react'
function Test() {
const [a, setA] = useState([1, 2, 3])
console.log(a)
return (
<div>
{a}
<button onClick={() => {
a.push(4)
setA(a)
}}>
테스트
</button>
</div>
)
}
export default Test
- 실제 렌더링이 안되는 것을 확인할 수 있다.
- 객체의 특성상 메모리 주소는 그대로이기 때문에, setA 를 하더라도 리액트가 값이 달라졌다고 인식을 못한다.
불변성 유지하며 배열 데이터 수정하기
- 불변성을 유지하기 위해 object.assign / filter / reduce / map 등의 배열, 객체와 관련한 내장 함수를 사용하는 방법이 있다.
- 아래는 영화 정보를 가져와 보여주는 컴포넌트 예제이다.
export const data = [
{title: "The Truth About the Harry Quebert Affair", year: "2018", id: "tt7134194"},
{title: "Harry and Tonto", year: "1974", id: "tt0071598"},
{title: "Harry & Meghan: The Complete Story", year: "2022–", id: "tt23016788"},
{title: "The Thing About Harry", year: "2020", id: "tt11324534"},
{title: "Harry Enfield and Chums", year: "1994–1999", id: "tt0108796"},
{title: "Harry Potter and the Forbidden Journey", year: "2010", id: "tt1756545"},
{title: "Harry Potter and the Chamber of Secrets", year: "2002", id: "tt0304140"},
{title: "Harry & Meghan: A Royal Romance", year: "2018", id: "tt7883022"},
{title: "The Harry Hill Movie", year: "2013", id: "tt3013528"},
{title: "Harry & Son", year: "1984", id: "tt0087386"}
];
import React, { useState } from 'react'
import { data as movies } from '../constants/data'
function Movies() {
return (
<div>
{movies.map((movie) => {
return (
<div key={movie.id}> // map 사용시 고유한 키값을 꼭 명시해야 함
<h1>{movie.title}</h1>
<h3>{movie.year}</h3>
</div>
)
})}
</div>
)
}
export default Movies
- 리액트에서 배열에 데이터를 추가할 경우 push가 아닌 spread 연산자 를 사용한다.
[...기존배열, 새로운 데이터]
- 버튼 클릭시 새로운 영화정보가 추가되는 걸 알 수 있다.
import React, { useState } from "react";
import { data } from "../constants/data";
function Movies() {
const [movies, setMovies] = useState(data);
const addMovie = (movie) => { setMovies([...movies, movie]) }; // spread 연산자사용
return (
<div>
{movies.map((movie) => {
return (
<div key={movie.id}>
<h1>{movie.title}</h1>
<h3>{movie.year}</h3>
<button onClick={() => addMovie({ id: "다음id번호", title: "영화제목", year: "개봉년도" })}> 영화 추가하기 </button>
</div>
);
})}
</div>
);
}
export default Movies;
- 데이터를 제거할 때는 filter 를 사용한다.
//영화정보를 제거하는 함수
const delMovie = (movieId) => {
setMovies(movies.filter((movie)=> movie.id !== movieId))
}
// map 함수안에 삭제버튼 추가
<button onClick={() => delMovie(movie.id)}>영화 제거하기</button>
- 데이터를 수정할 경우에는 삼항연산자와 함께 map을 사용한다.
const updMovie = (id, newMovie) => {
setMovies(movies.map((movie) => (movie.id === id ? newMovie : movie)));
};
<button onClick={() => updMovie(movie.id, { ...movie, title: "수정된 타이틀" })}>영화 수정하기</button>
요약하자면 데이터 추가할때는 spread 연산자, 삭제는 filter, 수정은 map과 삼항연산자를 사용한다.
20230228 패스트캠퍼스 FE4기 수업자료를 참고하였습니다.
'React' 카테고리의 다른 글
Styled-components (0) | 2023.03.21 |
---|---|
useRef (0) | 2023.03.20 |
이벤트핸들러 (0) | 2023.03.15 |
useEffect (0) | 2023.03.09 |
props, useState (0) | 2023.03.08 |