불변성이란?
리액트에서 객체와 배열 타입의 상태를 다둘 때는 불변성(immutability)을 지켜야 한다.
불변성을 지킨다는 것은 객체 또는 배열을 직접 수정하지 않는다는 것을 의미한다.
선언한 객체나 배열을 다른 값으로 변경하고 싶을때는 복제해 새로운 객체나 배열을 생성해야 한다.
불변성을 지켜야 하는 이유
리랙트에서 불변성을 지켜야 하는 이유는 렌더링 성능 최적화 방식 때문이다. 리액트에서는 부모 컴포넌트가 리렌더링(상태가 업데이트되어 다시 렌더링 되는 것)되면 기본적으로 자식 컴포넌트들 또한 리렌더링 된다.
문제는 변경사항이 없는 컴포넌트들도 리렌더링 된다는 것이다. 리액트는 최적화가 많이 되어있어서 리렌더링 된다고 성능에 부하가 발생하진 않지만, 컴포넌트에서 다루는 데이터가 많아지거나 연산량이 늘어난 경우는 컴포넌트에 변화가 발생했을때만 리렌더링 되도록 최적화 해야 한다.
그렇다면 어떻게 변화를 감지할 수 있을까?
Props(속성)의 변화를 통해 알 수 있다. 컴포넌트의 렌더링 성능을 최적화하기 위해서는 이전에 컴포넌트가 들고 있던 Props와 새로 받아올 Props를 비교하는 과정이 필요하다. 이 과정에서 불변성을 유지하는 것이 정말 중요하다.
const data = { id: 1, text: '안녕하세요.' };
const someData = data;
someData.text = '반갑습니다.';
console.log(someData === data); // true
위 코드에서 data 객체와 someData 객체는 똑같은 객체를 가리키고 있기 때문에 someData.text를 변경하게 되면 data.text도 변경된다. 이처럼 만약 불변성을 지키지 않고 코드를 작성하게 된다면 props의 변화 상태를 감지할 수 없으므로 렌더링 여부를 알 수 없다.
const data = { id: 1, text: '안녕하세요.' };
const someData = {
...data,
text: '반갑습니다.'
};
console.log(someData === data); // false
위 코드처럼 기존 data값을 spread 연산자를 새로운 객체로 덮어쓰워줬을땐 두 객체는 서로 완전히 다른 객체라고 할 수 있다.
객체의 불변성을 지키는 방법
만약 userInfo의 name값을 바꾼다면 아래와 같은 코드를 작성할 것이다.
const userInfo = {
id: 1,
name: 'beekei'
};
userInfo.name = 'devbeekei';
리액트에서 상태를 다룰 때는 객체가 지닌 값을 바꾸고 싶다고 해서 위에 코드처럼 직접 값을 수정하면 안된다.
그 대신 기존 객체는 그대로 두고, 아래 코드와 같이 새로운 객체를 만들어 원하는 값을 덮어씌워야 한다.
const newUserInfo = {
...userInfo,
name: 'devbeeke'
};
여기서 ...userInfo는 spread 연산자 문법이다. userInfo 객체가 지닌 값들을 newUserInfo 객체에 펼친다고 생각하면 된다. key가 같은 값은 덮어씌워진다.
배열의 불변성을 지키는 방법
리액트에서 배열 타입의 상태를 다룰 때에도 불변성을 지켜야 한다.
const numbers = [0, 1, 2, 3];
numbers.push(4); // 추가
numbers[0] = 10; // 수정
numbers.splice(0, 1); // 삭제
위 코드는 배열에 직접 추가, 수정, 삭제를 하고 있다.
리액트레서 상태르르 관리할 때는 이와 같이 코드를 작성하면 안된다. 그 대신 배열의 내장 함수들을 활용해 새로운 배열을 생성하는 방식으로 배열의 상태를 업데이트 해야 한다.
const numbers = [0, 1, 2, 3];
// spread 연산자로 추가
const numbersSpread1 = [...nmbers, 4]; // [0, 1, 2, 3, 4]
const numbersSpread2 = [...nmbers, 4, ...nmbers]; // [0, 1, 2, 3, 4, 0, 1, 2, 3]
// concat 함수로 추가
const numbersConcat1 = numbers.concat(4); // [0, 1, 2, 3, 4]
const numbersConcat2 = numbers.concat([4, 5, 6]); // [0, 1, 2, 3, 4, 5, 6]
// map 함수로 수정
const numbersMap1 = numbers.map(number => number === 1 ? 10 : number); // [0, 10, 2, 3]
const numbersMap2 = numbers.map((number, idx) => idx === 0 ? 10 : number); // [10, 1, 2, 3]
// filter 함수로 삭제
const numbersFilter1 = numbers.filter(number => number > 1); // [0, 1]
const numbersFilter2 = numbers.filter((number, idx) => idx != 0); // [1, 2, 3]
위 코드들은 불변성을 지키고 배열을 추가, 수정, 삭제하는 방법이다. 여러가지 방법이 있지만 권장하는 방법은 위와 같은 방법이다.
그렇다면 만약 배열 안에 객체가 있는 경우에는 어떻게 해야 할까?
const products = [
{ id: 1, name: "맥북" },
{ id: 2, name: "아이폰" },
{ id: 3, name: "갤럭시" }
];
// spread 연산자로 추가
const productsSpread = [...products, { id: 4, name: "그램노트북" }];
// [{"id":1,"name":"맥북"},{"id":2,"name":"아이폰"},{"id":3,"name":"갤럭시"},{"id":4,"name":"그램노트북"}]
// concat 함수로 추가
const productsConcat = products.concat({ id: 4, name: "그램노트북" });
// [{"id":1,"name":"맥북"},{"id":2,"name":"아이폰"},{"id":3,"name":"갤럭시"},{"id":4,"name":"그램노트북"}]
// map 함수로 수정
const productsMap = products.map((p) => p.id === 2 ? { ...p, name: "아이폰12" } : p);
// [{"id":1,"name":"맥북"},{"id":2,"name":"아이폰12"},{"id":3,"name":"갤럭시"}]
// filter 함수로 삭제
const productsFilter = products.filter((p) => p.id !== 3);
// [{"id":1,"name":"맥북"},{"id":2,"name":"아이폰"}]
// 복제 후 splice 함수로 삭제
const productsSplice = [...products].splice(0, 2);
// [{"id":1,"name":"맥북"},{"id":2,"name":"아이폰"}]
마지막에 splice 함수는 불변성을 지키는 함수가 아니지만 products 배열을 복제 후에 수정한 것이므로 불변성에 문제가 되지 않는다.
하지만 map을 사용해 수정하는 것이 가독성이 좋기 때문에 map을 사용해 수정하는 것을 권장한다.
'React Native' 카테고리의 다른 글
백터 아이콘(Vector Icons) 사용하기 (0) | 2022.02.06 |
---|---|
FlatList로 리스트 만들기 및 스크롤 감지 (0) | 2022.02.06 |
TextInput에 키보드 Enter 이벤트 설정하기 (0) | 2022.02.06 |
컴포넌트 터치 시 효과 주기 (0) | 2022.02.06 |
TextInput 컴포넌트 사용 및 키보드 가림 설정 (0) | 2022.02.06 |