Featured image of post JavaScript 불변성, 왜 중요할까? 쉽게 이해하는 React 준비 완벽 가이드

JavaScript 불변성, 왜 중요할까? 쉽게 이해하는 React 준비 완벽 가이드

자바스크립트 불변성의 모든 것을 실무 경험으로 쉽게 설명합니다. 원시타입과 참조타입의 차이점, 불변성이 중요한 이유, React에서 왜 필수인지, 그리고 불변성을 지키는 실전 방법까지 코드 예제로 완벽 마스터하세요.

💡 원시타입 vs 참조타입에 대해 미리 알고 계시면 이 글의 내용을 더 쉽게 이해하실 수 있습니다.

객체나 배열을 다른 변수에 복사했는데, 한쪽을 수정하니까 다른 쪽도 함께 바뀌어서 당황한 적이 있으신가요? 특히 함수에서 객체를 받아서 처리한 후 원본까지 바뀌어 버려서 예상치 못한 결과가 나온 경험도 있을 거예요.

저도 자바스크립트를 처음 배울 때 똑같은 문제로 고생했어요. 특히 간단한 할 일 관리 프로그램을 만들었는데, 할 일을 추가하는 함수에서 배열을 수정했더니 다른 함수에서도 그 변경 사항이 반영되어서 완전히 엉망이 되더라고요. 알고 보니 배열과 객체를 직접 수정했던 게 문제였죠. 그때 비로소 자바스크립트의 불변성(Immutability) 개념이 얼마나 중요한지 깨달았어요.

이 글에서는 제가 자바스크립트를 배우면서 경험한 구체적인 사례들과 함께 불변성의 모든 것을 알려드릴게요. 단순히 개념만 설명하는 게 아니라, 실제 코딩에서 바로 적용할 수 있는 실전 패턴들을 중심으로 다룰 예정입니다.

불변성의 기본 개념부터 원시타입과 참조타입의 차이점, 나중에 React 같은 라이브러리를 배울 때 왜 이 개념이 필수인지, 그리고 얕은 복사와 깊은 복사의 실무 활용법까지 단계별로 설명하겠습니다.



불변성이란? 실무에서 겪는 진짜 문제들

불변성(Immutability)은 데이터가 생성된 후에 변경되지 않는 성질을 말합니다. 한 번 만들어진 값이나 객체는 수정할 수 없고, 변경이 필요하면 새로운 값이나 객체를 만들어야 합니다.

실제 코딩을 할 때 이 개념이 정말 중요해요. 제가 작년에 간단한 학생 관리 프로그램을 만들었을 때, 학생 정보를 여러 함수에서 공유해야 하는 상황이 있었어요. 처음엔 객체를 그대로 전달했다가 한 함수에서 수정한 데이터가 다른 함수에도 영향을 미쳐서 예상치 못한 버그가 발생했어요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 원본은 건드리지 않고, 새로운 것을 만드는 것이 불변성!
let originalName = "철수";
let newName = "영희"; // 새로운 값 생성
console.log(originalName); // "철수" (원본 그대로)

// 실제 프로그래밍 예시 - 사용자 정보 처리
function processUserInfo(user) {
    // ❌ 이렇게 하면 원본이 변경됨
    // user.isProcessed = true;
    
    // ✅ 새로운 객체를 만들어 반환
    return { ...user, isProcessed: true };
}

불변성을 지키면 코드 흐름을 예측할 수 있고 안전하게 데이터를 다룰 수 있습니다. 원본 데이터가 언제 어디서 변경되는지 걱정할 필요가 없기 때문입니다.

그런데 여기서 중요한 점은, 자바스크립트의 모든 데이터 타입이 똑같이 동작하지는 않는다는 거예요. 원시타입과 참조타입의 차이를 이해해야 불변성을 제대로 활용할 수 있답니다.


원시타입은 기본적으로 불변 - 걱정할 필요 없어요!

자바스크립트의 원시타입(string, number, boolean 등)은 기본적으로 불변성을 가지고 있습니다. 이 부분은 정말 다행이에요, 개발자가 따로 신경 쓸 필요가 없으니까요.

실제 프로그래밍에서 사용자 ID나 상품 가격 같은 기본 데이터들을 처리할 때를 생각해 보세요. 이런 기본 데이터들은 안전하게 복사하고 전달할 수 있어요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let userName = "철수";
let copyName = userName; // 값 자체가 복사됨
copyName = "영희";

console.log(userName); // "철수" (원본 안전!)
console.log(copyName); // "영희"

// 실제 간단한 계산기 프로그램 예시
let currentScore = 85;
let bonusScore = currentScore + 10; // 95 (새로운 값)
console.log(currentScore); // 85 (원본 그대로)

let studentName = "김학생";
let displayName = studentName.toUpperCase(); // "김학생" (새로운 문자열)
console.log(studentName); // "김학생" (원본 그대로)

원시타입이 불변인 이유는 메모리에 값 자체가 저장되기 때문입니다. 변수에 새로운 값을 할당하면 완전히 새로운 값이 메모리에 생성되죠.

1
2
3
4
5
6
7
let num = 10;
let result = num + 5; // 새로운 값 15가 생성됨
console.log(num); // 10 (원본 그대로)

let text = "Hello";
let upperText = text.toUpperCase(); // 새로운 "HELLO" 생성
console.log(text); // "Hello" (원본 그대로)

💡 실무 팁: 원시타입을 사용할 때는 불변성을 신경 쓸 필요가 없습니다. 자바스크립트 엔진이 자동으로 처리해 주니까요. 하지만 객체와 배열은 완전히 다른 이야기예요!


참조타입은 기본적으로 가변 - 여기서 문제가 시작됩니다

반면 참조타입(객체, 배열)은 기본적으로 가변성을 가지고 있습니다. 이 부분이 바로 많은 초보자들이 헷갈리고 버그를 만들어내는 지점이에요.

제가 처음 자바스크립트를 배울 때 가장 많이 실수했던 부분이기도 합니다. 특히 간단한 주소록 프로그램을 만들 때 이런 코드를 짰었어요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
let userInfo = { name: "철수", age: 25 };
let copyInfo = userInfo; // 주소가 복사됨!
copyInfo.name = "영희";

console.log(userInfo); // { name: "영희", age: 25 } (원본 변경됨!)
console.log(copyInfo); // { name: "영희", age: 25 }

// 실제 프로그래밍에서 겪었던 문제 상황
let currentUser = { 
    id: 1, 
    name: "김개발", 
    skills: ["HTML", "CSS", "JavaScript"] 
};

function addSkill(user, skill) {
    user.skills.push(skill); // 원본 변경! 위험!
    return user;
}

let updatedUser = addSkill(currentUser, "React");
console.log(currentUser.skills); // ["HTML", "CSS", "JavaScript", "React"] (원본도 변경됨!)

참조타입이 가변인 이유는 메모리에 주소가 저장되기 때문입니다. 변수에는 실제 데이터가 아니라 데이터가 저장된 메모리 주소가 들어있어서, 같은 주소를 참조하는 모든 변수가 같은 데이터를 공유하게 됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let numbers = [1, 2, 3];
let moreNumbers = numbers; // 같은 배열을 가리킴
moreNumbers.push(4);

console.log(numbers); // [1, 2, 3, 4] (원본 변경됨!)
console.log(moreNumbers); // [1, 2, 3, 4]

// 실제 학생 성적 관리 프로그램에서 겪었던 문제
let studentScores = [
    { name: "김학생", score: 85 },
    { name: "이학생", score: 92 }
];

let backupScores = studentScores; // 위험한 복사!
studentScores[0].score = 90; // 원본 수정

console.log(backupScores[0].score); // 90 (백업본도 함께 변경됨!)

📝 실무에서 주의: 이런 현상을 “참조 공유"라고 하며, 의도치 않은 데이터 변경의 주요 원인입니다. 특히 나중에 React 같은 라이브러리를 배울 때 이로 인해 상태 업데이트가 제대로 감지되지 않는 문제가 발생할 수 있어요.

이제 왜 불변성이 이렇게 중요한지, 그리고 어떤 장점들이 있는지 구체적으로 알아볼까요?


왜 불변성이 중요할까? 실제 코딩에서 겪은 진짜 이유들

불변성을 지키면 다음과 같은 장점들을 얻을 수 있습니다. 저도 이 원칙들을 지키기 시작한 후부터 버그가 확연히 줄어들었어요.


1. 예측할 수 있는 코드 - 디버깅이 훨씬 쉬워져요

제가 간단한 게임 점수 관리 프로그램을 만들 때 겪었던 일인데요, 여러 함수가 같은 플레이어 데이터를 다루다 보니 누가 언제 데이터를 변경했는지 추적하기가 정말 어려웠어요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function processPlayer(player) {
    // 원본을 변경하지 않으므로 안전
    let processedPlayer = { ...player, processed: true, processedAt: new Date() };
    return processedPlayer;
}

// 실제 게임 점수 관리에서 사용한 패턴
function updatePlayerScore(player, newScore) {
    // ❌ 이렇게 하면 원본이 변경되어 다른 함수에 영향
    // Object.assign(player, { score: newScore });
    
    // ✅ 새로운 객체를 만들어 반환하면 안전
    return { ...player, score: newScore, updatedAt: new Date() };
}

let currentPlayer = { name: "철수", score: 100, level: 1 };
let updatedPlayer = updatePlayerScore(currentPlayer, 150);
console.log(currentPlayer); // 원본이 안전하게 보존됨
console.log(updatedPlayer); // 새로운 데이터로 업데이트됨

2. 버그 방지 - 사이드 이펙트 없애기

공유 상태를 여러 함수에서 수정하면 예상치 못한 부작용이 발생할 수 있어요. 실제로 제가 간단한 온라인 상점 시뮬레이터를 만들 때 겪었던 문제였는데, 할인 계산 함수와 세금 계산 함수가 같은 상품 객체를 수정해서 최종 가격이 이상하게 나온 적이 있었어요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ❌ 위험한 방식 - 공유 상태 직접 수정
let gameState = { score: 0, lives: 3, items: [] };

function addPoints(points) {
    gameState.score += points; // 위험! 다른 함수에 영향
    gameState.items.push('점수아이템');
}

function loseLife() {
    gameState.lives--; // 위험! 예상치 못한 부작용
    gameState.items.push('생명감소');
}

// ✅ 안전한 방식 - 새로운 상태 반환
function safeAddPoints(state, points) {
    return {
        score: state.score + points,
        lives: state.lives,
        items: [...state.items, '점수아이템']
    };
}

function safeLoseLife(state) {
    return {
        score: state.score,
        lives: state.lives - 1,
        items: [...state.items, '생명감소']
    };
}

3. 함수의 신뢰성 높이기 - 언제 어디서든 안전하게 사용

불변성을 지키는 함수는 언제 어디서 호출해도 예상대로 동작해요. 이건 정말 중요한 장점이에요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// ❌ 위험한 함수 - 원본을 변경함
function dangerousAddStudent(students, newStudent) {
    students.push(newStudent); // 원본 배열 변경!
    return students;
}

// ✅ 안전한 함수 - 새로운 배열 반환
function safeAddStudent(students, newStudent) {
    return [...students, newStudent]; // 새 배열 생성
}

// 사용 예시
let myStudents = [
    { name: "김학생", grade: "A" },
    { name: "이학생", grade: "B" }
];

let updatedStudents = safeAddStudent(myStudents, { name: "박학생", grade: "A" });
console.log(myStudents.length); // 2 (원본 그대로)
console.log(updatedStudents.length); // 3 (새로운 배열)

4. 나중에 React 배울 때 필수 개념!

지금은 순수 자바스크립트를 배우고 있지만, 나중에 React를 배우게 되면 불변성이 정말 중요해요. React는 데이터 변경을 감지하기 위해 참조 비교를 사용하거든요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// React에서 이런 식으로 상태를 관리해요 (미리 보기)
// ❌ React에서 잘못된 방식
const todos = [
    { id: 1, text: '자바스크립트 공부', done: false },
    { id: 2, text: 'React 공부', done: false }
];

// 이렇게 하면 React가 변경을 감지하지 못함
todos.push({ id: 3, text: '새 할 일', done: false });

// ✅ React에서 올바른 방식
const newTodos = [...todos, { id: 3, text: '새 할 일', done: false }];

💡 미래를 위한 팁: 지금부터 불변성을 지키는 습관을 들이면, 나중에 React나 Vue 같은 프레임워크를 배울 때 훨씬 수월할 거예요!

이제 실제로 불변성을 지키는 구체적인 방법들을 알아볼까요? 이론만 알아서는 소용없으니, 바로 코딩에 적용할 수 있는 실전 패턴들을 살펴보겠습니다.


불변성을 지키는 방법 - 코딩에서 바로 쓰는 패턴들

참조타입에서 불변성을 지키려면 원본을 수정하지 말고, 새로운 객체나 배열을 만들어야 합니다. 저는 이 패턴들을 익히고 나서 코드 작성 속도도 빨라지고 버그도 확실히 줄어들었어요.


객체 복사하기 - 사용자 정보 업데이트 실전 패턴

실제 프로그래밍에서 가장 많이 사용하는 패턴들을 소개할게요. 특히 게임 캐릭터 정보나 학생 데이터 수정에서 자주 쓰이는 방법들이에요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
let player = { name: "철수", level: 5, exp: 1250, hp: 100 };

// 방법 1: 수동으로 새 객체 생성 (단순한 경우)
let leveledUpPlayer = {
    name: player.name,
    level: player.level + 1,
    exp: player.exp,
    hp: player.hp
};

// 방법 2: Object.assign() 사용 (ES5 방식)
let healedPlayer = Object.assign({}, player, { hp: 100 });

// 방법 3: 스프레드 연산자 (가장 널리 사용)
let modernPlayer = { ...player, level: 6, exp: 1500 };

console.log(player); // 원본 보존
console.log(modernPlayer); // 새로운 객체

// 실제 게임에서 자주 사용하는 캐릭터 업데이트 함수
function updatePlayerStats(player, updates) {
    return {
        ...player,
        ...updates,
        lastUpdated: new Date().toISOString()
    };
}

// 중첩된 객체 업데이트 (실제 RPG 게임 형태)
let gameCharacter = {
    name: "용사김씨",
    stats: {
        strength: 15,
        intelligence: 12
    },
    inventory: {
        weapons: ["검", "방패"],
        potions: 5
    }
};

// 중첩된 객체의 일부만 업데이트하기
let upgradeCharacter = {
    ...gameCharacter,
    stats: {
        ...gameCharacter.stats,
        strength: 18
    }
};

배열 복사하기 - 목록 관리 실전 패턴

배열 관련 작업은 프로그래밍에서 정말 자주 사용해요. 제가 할 일 관리 앱과 간단한 블로그를 만들면서 가장 많이 사용했던 패턴들입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
let scores = [85, 92, 78];
let students = [
    { id: 1, name: "김학생", grade: "A" },
    { id: 2, name: "이학생", grade: "B" }
];

// 배열에 새 요소 추가
let newScores = [...scores, 95]; // [85, 92, 78, 95]
let addedStudent = [...students, { id: 3, name: "박학생", grade: "A" }];

// 배열 앞에 추가
let prependedScores = [100, ...scores]; // [100, 85, 92, 78]

// 특정 위치에 삽입
let insertedScores = [...scores.slice(0, 1), 90, ...scores.slice(1)]; // [85, 90, 92, 78]

// 배열 요소 수정 (map 활용)
let bonusScores = scores.map(score => score + 5); // [90, 97, 83]

// 실제 예시: 학생 성적 업데이트
function updateStudentGrade(students, studentId, newGrade) {
    return students.map(student => 
        student.id === studentId 
            ? { ...student, grade: newGrade }
            : student
    );
}

// 배열에서 요소 제거
function removeStudent(students, studentId) {
    return students.filter(student => student.id !== studentId);
}

// 배열 정렬 (원본 보존)
let sortedStudents = [...students].sort((a, b) => a.name.localeCompare(b.name));

console.log(scores); // [85, 92, 78] (원본 보존)
console.log(students); // 원본 배열 그대로

스프레드 연산자 간단 소개

최신 자바스크립트에는 스프레드 연산자(…)라는 편리한 문법이 있습니다. 이를 사용하면 불변성을 지키면서도 코드를 간단하게 작성할 수 있어요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let player = { name: "철수", level: 5 };
let numbers = [1, 2, 3];

// 객체 복사 + 수정
let newPlayer = { ...player, level: 6 };

// 배열 복사 + 추가
let newNumbers = [...numbers, 4];

console.log(player); // 원본 보존
console.log(newPlayer); // 새로운 객체

스프레드 연산자는 앞서 배운 방법들보다 훨씬 간단하고 직관적이어서 현재 가장 많이 사용되는 방법입니다.

💡 다음 글 예고: 스프레드 연산자의 고급 활용법과 성능 최적화 방법은 별도의 글에서 자세히 다룰 예정이니, 지금은 “이런 편리한 방법이 있구나” 정도로만 이해하셔도 됩니다.

하지만 스프레드 연산자도 주의해야 할 점이 있어요. 바로 얕은 복사와 깊은 복사의 차이인데, 이 부분을 간단히 알아보겠습니다.


얕은 복사와 깊은 복사 - 개념만 간단히

스프레드 연산자를 사용할 때 주의해야 할 점이 있어요. 바로 얕은 복사의 한계입니다.

1
2
3
4
5
6
7
8
let player = { 
    name: "철수", 
    skills: ["공격", "방어"],
    equipment: { weapon: "검", armor: "갑옷" }
};

let copyPlayer = { ...player }; // 얕은 복사
copyPlayer.skills.push("마법"); // 위험! 원본도 변경됨

중첩된 객체나 배열이 있을 때는 깊은 복사가 필요합니다.

1
2
3
4
5
6
7
8
9
// 안전한 방법 1: 수동으로 각 레벨 복사
let safePlayerCopy = {
    ...player,
    skills: [...player.skills],
    equipment: { ...player.equipment }
};

// 안전한 방법 2: JSON 방식 (간단하지만 한계 있음)
let deepCopyPlayer = JSON.parse(JSON.stringify(player));

📚 자세한 내용은 다음 글에서: 깊은 복사의 다양한 방법들과 각각의 장단점은 별도 포스팅에서 자세히 다룰 예정이에요!


함수 매개변수 주의 - 초보자가 자주 하는 실수

함수에서 매개변수로 받은 객체를 직접 수정하는 것은 정말 위험해요. 저도 초보 시절에 이런 실수를 자주 했었는데, 디버깅하기가 정말 어려웠어요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// ❌ 위험한 방식 - 매개변수 직접 수정
function processStudent(student) {
    student.lastAccess = new Date(); // 원본 변경! 위험!
    student.visitCount = (student.visitCount || 0) + 1;
    return student;
}

// 실제 사용 시 문제 발생
let currentStudent = { name: "김학생", grade: "A" };
let processedStudent = processStudent(currentStudent);
console.log(currentStudent); // 원본도 변경됨!

// ✅ 안전한 방식 - 새 객체 반환
function safeProcessStudent(student) {
    return { 
        ...student, 
        lastAccess: new Date(),
        visitCount: (student.visitCount || 0) + 1
    };
}

// 배열 처리에서도 마찬가지
function processStudentList(students) {
    // ❌ 원본 배열 직접 수정
    // students.forEach(student => student.processed = true);
    
    // ✅ 새 배열 반환
    return students.map(student => ({ ...student, processed: true }));
}

// 게임 개발에서 자주 사용하는 안전한 패턴
function calculateDamage(character, damage) {
    // 원본 캐릭터를 변경하지 않고 새로운 상태 반환
    const newHp = Math.max(0, character.hp - damage);
    return {
        ...character,
        hp: newHp,
        isAlive: newHp > 0
    };
}

💡 코딩 팁: 함수에서 매개변수를 받을 때는 항상 “이 함수가 원본 데이터를 변경해도 괜찮을까?“를 생각해 보세요. 99%의 경우 새로운 값을 반환하는 것이 더 안전합니다.


💬 자주 묻는 질문 (FAQ)

실무에서 불변성을 적용하면서 자주 받는 질문들을 정리해 봤어요. 저도 처음 배울 때 궁금했던 것들이 대부분이에요.


Q1. 불변성을 지키면 성능이 떨어지지 않나요?

A: 좋은 질문이에요! 저도 처음엔 같은 걱정을 했었어요.

새로운 객체나 배열을 계속 만들면 메모리를 많이 사용할 것 같지만, 실제로는 그렇지 않아요. 자바스크립트 엔진과 React 같은 라이브러리들이 최적화를 잘해주거든요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 이런 경우엔 실제로는 공유하는 부분이 많아요
let user1 = { name: "철수", hobbies: ["독서", "영화"] };
let user2 = { ...user1, name: "영희" }; // hobbies 배열은 공유됨

// React에서는 오히려 성능이 더 좋아져요
function ExpensiveComponent({ user }) {
    // 불변성을 지키면 React.memo가 제대로 작동
    return <div>{/* 복잡한 렌더링 로직 */}</div>;
}

export default React.memo(ExpensiveComponent); // 얕은 비교로 빠른 최적화

실무에서 제가 측정해 본 결과, 불변성을 지키는 것이 오히려 성능 최적화에 도움이 되는 경우가 더 많았어요.


Q2. 깊은 복사는 언제 필요하고, 어떤 방법이 가장 좋은가요?

A: 중첩된 객체나 배열이 있을 때 필요해요. 하지만 항상 깊은 복사가 필요한 건 아니에요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 이런 경우엔 얕은 복사로 충분
let user = { name: "철수", age: 25, city: "서울" };
let newUser = { ...user, age: 26 };

// 이런 경우엔 깊은 복사 필요
let complexUser = {
    name: "철수",
    settings: {
        theme: "dark",
        notifications: ["email", "push"]
    }
};

// 간단한 방법
let safeCopy = {
    ...complexUser,
    settings: {
        ...complexUser.settings,
        theme: "light"
    }
};

📚 자세한 내용은 다음 글에서: 깊은 복사의 다양한 방법들과 각각의 장단점은 별도 포스팅에서 자세히 다룰 예정이에요!


Q3. 자바스크립트에서 배열 업데이트할 때 자주 실수하는 부분이 있나요?

A: 네, 정말 많아요! 저도 초보 시절에 이런 실수를 자주 했어요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// ❌ 자주 하는 실수들
let todoList = [
    { id: 1, text: '자바스크립트 공부', done: false },
    { id: 2, text: '프로젝트 만들기', done: false }
];

// 실수 1: 원본 배열 직접 수정
function addTodoWrong(todos, newTodo) {
    todos.push(newTodo); // 이러면 원본이 변경됨!
    return todos;
}

// 실수 2: 인덱스로 직접 수정
todoList[0].done = true; // 객체 내부 직접 수정

// ✅ 올바른 방법들
// 새 할 일 추가
function addTodo(todos, newTodo) {
    return [...todos, newTodo]; // 새 배열 생성
}

// 할 일 완료 상태 변경
function toggleTodo(todos, targetId) {
    return todos.map(todo => 
        todo.id === targetId 
            ? { ...todo, done: !todo.done } // 새 객체 생성
            : todo
    );
}

// 할 일 삭제
function removeTodo(todos, targetId) {
    return todos.filter(todo => todo.id !== targetId);
}

// 할 일 정렬 (원본 보존)
function sortTodos(todos) {
    return [...todos].sort((a, b) => a.text.localeCompare(b.text));
}

코딩 팁: 배열을 다룰 때는 push(), pop(), splice() 같은 원본을 변경하는 메서드 대신, map(), filter(), concat(), 스프레드 연산자를 사용하세요.



이 글이 자바스크립트 불변성을 이해하는 데 도움이 되셨나요? 불변성은 처음에는 번거로워 보일 수 있지만, 안전하고 예측할 수 있는 코드를 작성하는 데 꼭 필요한 개념이에요.

특히 여러분이 코딩하면서 겪었던 비슷한 경험이나, 이 글을 읽고 해결된 문제가 있다면 댓글로 공유해주세요!

다음 글에서는 **JavaScript 얕은 복사 vs 깊은 복사**에 대해 다뤄보겠습니다. 불변성을 지키기 위해 꼭 알아야 하는 복사 방법들, 특히 2025년 최신 방법인 structuredClone()까지 실무 예제와 함께 완벽하게 정리해 드릴게요. 중첩된 객체나 배열로 고생하셨던 분들은 꼭 놓치지 마세요!

함께 성장하는 개발자가 되어요! 🚀


Hugo로 만듦
JimmyStack 테마 사용 중