리액트의 상태가 불변성 관리를 하는 이유는 크게 두가지이다.
- 값이 변경됐는지 알기 위해서
- 값을 수정하면, 이전 상태와 바뀐 상태값이 동일해져서 비교할 수 없어서
리액트에서는 상태가 변하면 컴포넌트가 리렌더링된다.
중요‼️
리액트에서 리렌더링할 때 값 자체가 아니라 참조 값을 비교하므로 참조 값이 동일하면 변경을 감지할 수 없다.
값을 수정하면 이전 상태와 바뀐 상태값이 동일해지기 때문에 이전 상태와 현재 상태를 비교하여 렌더링할 수 없게 된다.
assign 메서드
spread 연산자
라이브러리
Object.assign(target, ...sources)
첫 번째 인자는 타겟, 두 번째 인자부터 마지막 인자까지는 소스 오브젝트이다.
소스 오브젝트는 타겟 오브젝트에 병합되고 리턴값으로 타겟오브젝트를 반환한다.
예시1
const one = {a:1};
const arr = Object.assign({}, one);
console.log(arr); // {a: 1}
예시2
const one = {a:1};
const two = {b:2};
const three = {c:3};
const arr = Object.assign({}, one, two, three);
console.log(arr); // {a: 1, b: 2, c: 3}
// 어떠한 인수를 전달하고 싶을때 원래 사용하는 방식은 다음과 같다 //
function showName(name){
console.log(name);
}
showName('Mike'); // 'Mike'
showName('Mike', 'Tom'); // 'Mike' 첫번째 값만 찍힘 하지만 에러는 나지 않음
showName(); // undefined 에러는 나지 않음
// 이처럼 값이 여러개 값이 있을경우 잘 전달하지 못한다. //
// 나머지 매개변수 //
// 1번 예시 [값을 단순히 전달하고 싶을때] //
function showName(...names){
console.log(names);
}
showName(); // []
showName('Mike'); // ['Mike']
showName('Mike', 'Tom'); // ['Mike', 'Tom']
// 나머지 매개변수 //
// 2번 예시 [전달 받은 모든 수를 더하고싶을때] //
function add(...numbers) {
let answer = numbers.reduce( (arr, cur) => arr + cur );
console.log(answer);
}
add(1, 2, 3); // 6
add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55
// 나머지 매개변수 //
// **3번 예시** [user 객체를 만들어 주는 생성자 함수를 만들때] //
function User(name, age, ...skills){
this.name = name;
this.age = age;
this.skills = skills;
}
const user1 = new User("mike", 30, "html", "css");
const user2 = new User("Tom", 20, "java", "spring");
const user3 = new User("Jane", 10, "c++");
console.log(user1); // User { name: 'mike', age: 30, skills: [ 'html', 'css' ] }
console.log(user2); // User { name: 'Tom', age: 20, skills: [ 'java', 'spring' ] }
console.log(user3); // User { name: 'Jane', age: 10, skills: [ 'c++' ] }
// 주의할 점 //
// 나머지 매개변수를 입력 할 때 스프레드문법이 가장 뒤에 와야한다. //
// 그 이유는 남은 값들을 모조리 끌어다가 배열로 묶기 때문이다. //
전개 구문
// 전개 구문 : 배열 //
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let answer = [...arr1, ...arr2];
// ...arr1 = 1,2,3
// ...arr2 = 4,5,6
console.log(answer); // [1, 2, 3, 4, 5, 6]
// 전개 구문 : 객체 //
let user = {name : 'Mike'}
let mike = {...name, age : 30}
console.log(mike) // {name : 'Mike', age : 30}
// 전개 구문 : 복제 //
let arr = [1, 2, 3];
let arr2 = [...arr]; // [1,2,3] 원래 배열엔 아무런 영향을 주지 않음
let user = {name : "Mike", age : 30};
let user2 = {...user};
user2.name = "Tom";
console.log(user); // { name: 'Mike', age: 30 }
console.log(user2); // { name: 'Tom', age: 30 }
// 원래 배열엔 아무런 영향을 주지 않고 name만 변경
// 전개구문 : 객체 심화 //
let user = { name : "Mike" };
let info = { age : 30 };
let skill = ["JS", "React"];
let lang = ["Korean", "English"];
user = {
...user,
...info,
skills : [...skill, ...lang],
}
console.log(user);
// console.log(user) 값
{
name: 'Mike',
age: 30,
skills: [ 'JS', 'React', 'Korean', 'English' ]
}
위 두 가지 방식을 사용하면 얕은 복사가 된다. 그러므로 객체 안의 객체가 있으면 그 안의 객체도 복사해주어야 한다.
하지만 무분별하게 깊은 복사를 사용하는 것은 다음과 같은 이유로 문제가 될 수 있다.
깊은 복사는 성능이 저하될 수 있다.
리액트에서 변경되지 않은 객체도 변경되었다고 감지하여 모든 것을 리렌더링할 수 있다.
그러므로, 변경된 객체만 변경해주는 것이 좋다.
이를 편하게 하고자 immer.js와 같은 라이브러리를 사용하여 쉽게 불변성을 관리할 수 있다.