React

불변성

soheedev 2023. 3. 8. 21:38

자바스크립트에서의 불변성이란?

- 원시형 (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