type="nal"

리액트 블로그-4. state가 array/object인 경우(얕은 복사) 본문

Web Development/React

리액트 블로그-4. state가 array/object인 경우(얕은 복사)

nalmi 2025. 1. 4. 16:06

지난 글의 배열 복사의 추가 셜명.,,

 

JS의 array, object 데이터의 특징

 

let arr = [1, 2, 3];

 

이런 배열이 있으면

1, 2, 3 데이터가 저장되어있고(RAM에), arr에는 이 데이터가 저장된 주소 정보가 저장되어 있음(어디있는지 가리키는 화살표)

 

1. 원본 배열 수정

    lkes[i]++;
    setLikes(likes);

그래서 그냥 이렇게 배열을 변경하려 한다면

likes[i] 안에 있는 내용은 바뀌더라도 likes라는 변수에는 여전히 같은 주소(화살표)가 저장되어 있음

따라서 기존 state와 수정된 state는 여전히 같기 때문에 state가 변경되지 않는다.

 

그렇기 때문에 기존 배열의 복사본을 만들어서 수정해주어야 하는데

 

2. 참조변수를 복사해 수정

const newLikes = likes;
    newLikes[i]++;
    setLikes(newLikes);

이렇게 하면 newLikes와 likes는 다른 배열인 것 같지만

newLikes에 배열을 복사하면 likes 변수 에 있던 화살표가 복사되는 것이기 때문에

결국 두 변수에는 같은 값이 들어있고, 새 배열을 수정하면 기존 배열도 변경되는 것이다.

당연히 state도 변경이 되지 않는다.

 

실제로 비교값을 콘솔에 출력해보면 true가 나온다.

  const increaseLike = (i) => {
    const newLikes = likes;
    newLikes[i]++;

    console.log(newLikes == likes);

    setLikes(newLikes);
  }

 

=> array, object는 reference data type 즉, 참조타입이라서 그럼

 

  • 원시 타입: 변수가 값 자체를 저장
  • 참조 타입: 변수가 메모리 주소(참조)를 저장

 

3. 배열을 얕은 복사로 새롭게 생성

그래서 화살표를 새로 바꾸려면 이렇게 코드를 작성해야 한다.

  const increaseLike = (i) => {
    const newLikes = [...likes];
    newLikes[i]++;
    setLikes(newLikes);
  }

 

...은 곧 배열의 괄호를 제거하고 내부 요소들을 나열하는 문법이고

여기에 다시 괄호를 씌워서 새로운 배열을 생성한다. 

새로운 메모리 공간이 할당되어 새로운 참조(화살표)가 생성되고, 결과적으로 원본 배열과 독립된 새로운 배열이 만들어진다.

 

 

 

이렇게 새로운 화살표를 set에 넣으면 새로운 state로 변경이 된다.

 

=> state가 arrayobjectshallow copy(얕은 복사)를 만들어서 수정해야 한다.

... Spread Operator)문법처럼, 얕은 복사는 1단계 깊이의 데이터만 복사하기 때문에

아래처럼 중첩된 데이터들은 원본 데이터를 참조하고 있다.

let original = {
  name: 'Kim',
  arr: [1, 2, 3],
  obj: { age: 25 }
};

// Shallow Copy 수행
let shallow = { ...original };

shallow.name = 'Lee';     // ✅ 원본에 영향 없음
shallow.arr[0] = 4;       // ❌ 원본도 변경됨
shallow.obj.age = 30;     // ❌ 원본도 변경됨

 

깊은 복사(deep copy)의 경우에는 원본과 완전히 독립된 복사본을 만들지만, 성능비용이 발생한다.

// 방법 1: JSON 사용 (가장 간단하지만 제한적)
let deep = JSON.parse(JSON.stringify(original));

// 방법 2: 재귀 함수로 구현
function deepCopy(obj) {
  if (obj === null || typeof obj !== 'object') 
    return obj;
  
  let copy = Array.isArray(obj) ? [] : {};
  
  for (let key in obj) {
    copy[key] = deepCopy(obj[key]);
  }
  
  return copy;
}

let deep = deepCopy(original);

deep.name = 'Lee';      // ✅ 원본에 영향 없음
deep.arr[0] = 4;        // ✅ 원본에 영향 없음
deep.obj.age = 30;      // ✅ 원본에 영향 없음

 

대부분의 경우 Shallow Copy로 충분하다.

 


숙제: 버튼 누르면 가나다순으로 정렬하기

=> state를 정렬하는 코드만 작성하면 알아서 잘 렌더링 됨.

배열을 문자열로 정렬해주는 sort()메서드를 사용했다.

      <button onClick={() => {
        const newTitles = [...titles];
        newTitles.sort();
        setTitles(newTitles);
      }}>가나다순으로</button>

 

숫자의 경우 문자열로 변환되어 정렬되기 때문에, 정렬 기준을 적어주어야 한다.

// 1. 기본 sort()
let titles = ['C', 'A', 'B'];
titles.sort();  // ['A', 'B', 'C'] - 알파벳 순 정렬

// 하지만 숫자는 예상과 다르게 동작할 수 있음
let numbers = [10, 2, 1];
numbers.sort();  // [1, 10, 2] - 문자열로 변환 후 정렬되기 때문

// 2. 올바른 숫자 정렬
numbers.sort((a, b) => a - b);  // [1, 2, 10] - 오름차순
numbers.sort((a, b) => b - a);  // [10, 2, 1] - 내림차순

(a, b) => a - b는 숫자 연산이고

sort() 메서드 비교 함수의 반환값은

  •  음수(-) 반환: a를 b보다 앞에 정렬
  • 0 반환: 순서 유지
  • 양수(+) 반환: a b보다 뒤에 정렬

이렇기 때문에 오름차순 정렬에서 a > b이면 a가 뒤로 정렬된다.