리액트에서 객체와 배열 타입의 상태를 다룰 때에는 불변성을 지켜야 한다. 불변성이란, 객체 또는 배열을 직접 수정하지 않는다는 것을 의미한다.
가령 다음과 가은 객체가 있다고 가정해보자.
const data = {
id: 1,
name: seongmin,
}
이 때, data.name
의 값을 바꾸려 한다면 대표적으로 다음과 같은 코드를 작성할 수 있다.
// 1.
data.name = "react"
// 2.
const newData = {
...data, // 스프레드 연산자 사용
name: "react"
}
위 두 코드 중 첫 번째 방법은 객체의 값을 직접 변경한, 불변성을 지키지 않은 코드이다.
그렇다면 불변성을 지켜야 하는 이유는 무엇인가?
리액트는 부모 컴포넌트의가 변경되어 리랜더링(re-randering) 되면 자식 컴포넌트 역시 리랜더링 된다. 중요한 것은 자식 컴포넌트의 상태가 변경되지 않아도 무조건적으로 리랜더링된다는 것이다. 그래서 컴포넌트에서 다루는 데이터의 양이 많아지거나 연산량이 증가하여, 부모 컴포넌트가 리랜더링 될 때마다 모든 자식 컴포넌트가 리랜더링 된다면 성능에 부하가 발생할 것이다. 이 때, 컴포넌트의 Props
를 통해 위와 같은 불상사를 방지할 수 있다.
컴포넌트가 이전에 갖고 있던 Props
와 새로 받아온 Props
를 비교하여 변화가 일어났을 때만 리랜더링하여 리액트의 랜더링 성능을 최적화할 수 있다. 이 과정에서 불변성을 유지하는 것이 중요해진다.
다음과 같은 코드가 있다.
const data = {
id: 1,
name: "seongmin",
}
const newData = data
newData.name = "react"
console.log(data === newData); // true
위와 같은 결과가 발생하는 이유는 바로 리액트 객체의 불변성 때문이다. data
와 newData
는 변수명이 다를지라도 불변성을 지키지 않았기 때문에 같은 객체를 가리키고 있다. 그래서 newData
의 name
속성을 변경하면 data.name
의 값도 변경이 되고, 이를 비교했을 때 true
가 반환되는 것이다.
불변성을 유지하면서 상태를 업데이트하려면 다음과 같이 코드를 작성할 수 있다.
const data = {
id: 1,
name: "seongmin",
}
const newData = {
...data,
name: "react",
}
console.log(data === newData); // false
이제 data
와 newData
는 서로 다른 객체를 가리키고, newData
의 값을 변경해도 data
의 값은 변경되지 않는다.
배열 타입의 상태를 다룰 때에도 불변성을 유지해야 한다. 리액트 배열의 내장 함수를 사용하면 쉽게 상태를 변경할 수 있다.
새로운 항목 추가
// 스프레드 연산자 활용
const arr = [1, 2, 3, 4, 5]
const nextArr = [...arr, 6] // [1, 2, 3, 4, 5, 6]
// Array.concat() 함수 활용
const arr = [1, 2, 3, 4, 5]
const nextArr = arr.concat([6, 7, 8]) // [1, 2, 3, 4, 5, 6, 7, 8]
항목 제거
Array.filter()
함수는 특정 조건을 만족하는 요소를 모아서 새로운 배열을 반환한다.
// Array.filter()
const arr = [-3, -2, -1, 0, 1, 2, 3]
const newArr = arr.filter(nums => nums > 0) // [1, 2, 3]
Array.findIndex()
함수는 특정 조건을 만족하는 첫 번째 요소의 index
를 반환한다. 이를 Array.splice()
함수와 함께 사용하면 특정 항목을 제거할 수 있다. Array.splice()
함수는 두 개의 매개변수를 받는다. 첫 번째 매개변수는 제거할 항목의 인덱스를 의미하고, 두 번째 매개변수는 제거할 항목의 인덱스로부터 몇 번째 인덱스 전까지 제거할 것인지를 알려 준다.
const arr = [-3, -2, -1, 0, 1, 2, 3]
const index = arr.findIndex(nums => nums === 0) // 3
const newArr = arr.splice(index, 1) // [-3, -2, -1, 1, 2, 3]
항목 수정
Array.map()
함수를 사용하면 편리하게 수정할 수 있다. 기본적으로 Array.map()
함수는 배열의 모든 요소에 관여하지만, 조건문을 사용한다면 특정 항목만 수정하는 것이 가능하다.
const arr = [-2, -1, 0, 1, 2]
const newArr = arr.map(num => num === 0 ? 10 : num) // [-2, -1, 10, 1, 2]
import { FlatList } from 'react-native
data
속성의 값으로 배열을 설정하고, renderItem
속성을 통해 배열 안의 각 원소 데이터에 접근할 수 있다.
keyExtractor
속성은 각 항목의 고유값을 추출하는 값으로, react
의 key props
와 동일하다고 이해하면 될 것 같다. 이 때, keyExtractor
의 값은 항상 문자열 타입이어야 하기 때문에 Number
타입일 경우 Object.toString()
메서드를 호출하여 문자열을 반환해주어야 한다.
Alert API
의 alert()
함수를 통해 사용자에게 알림창을 띄울 수 있고, 이 때 되돌아오는 응답에 따라 여러가지 작업을 실행할 수 있다.
const remove = () => {
Alert.alert(
'삭제', // 제목
'정말로 삭제하시겠습니까?', // 질문
[
{text: '취소', onPress: () => {}, style: 'cancle'},
{
text: '삭제',
onPress: () => {
remove // 삭제 함수 호출
},
style: 'destructive'
},
],
{canclable: true, onDismiss: () => {}},
);
}
alert()
함수는 총 네 개의 매개변수를 받는다. 첫 번째, 두 번째 매개변수는 각각 알림창의 제목과 질문을 의미한다. 세 번째 매개변수는 선택지와, 그 선택지를 선택했을 때 실행되는 함수, 선택지의 스타일 등의 정보가 포함된 배열이다. 마지막으로 네 번째는 Option
객체로, canclable
값을 통해 Alert
박스 바깥 영역을 터치하거나 취소 버튼을 눌렀을 때 Alert
창이 닫히도록 설정할 수 있다. 이 때, onDismiss
는 Alert
창이 닫힐 때 호출되는 함수이다.
브라우저에서 사용하는 LocalStorage
와 매우 유사하다. LocalStorage
에서 사용하는 getItem
, setItem
, clear
등과 동일한 메서드가 존재하고, 값을 저장할 때도 LocalStorage
와 마찬가지로 문자열 타입으로 저장해야 한다.
다만, AsyncStorage
는 비동기적으로 작동한다는 것이 LocalStorage
와 비교되는 가장 큰 차이점이다. 그래서 AsyncStorage
는 값을 조회하거나 설정할 때 Promise
를 반환한다.
라이브러리 설치
이전에는 AsyncStorage
가 React-Native
에 내장되어 있었지만, 현재에는 라이브러리로 분리되어 있기 때문에 사용하려면 별도로 설치해주어야 한다.
$ yarn add @react-native-community/async-storage
최대 용량 설정
Android
에서는 너무 많은 데이터를 저장하지 못하도록 AsyncStorage
의 최대 용량을 6MB로 제한하고 있다. 최대 용량 설정을 변경하고자 한다면 android/gradle.properties
파일에 다음과 같은 코드를 추가하면 된다.
AsyncStorage_db_size_in_MB=10
한계
AsyncStorage
는 문자열 타입으로만 데이터를 저장할 수 있기 때문에, 다루는 데이터의 용량이 커질수록 성능이 떨어진다.
따라서 소규모 데이터를 다루는 경우에만 AsyncStorage
를 사용하는 것이 권장되고, 데이터의 규모가 커진다면 realm
이나 react-native-sqlite-storage
의 사용을 고려하는 것이 좋다.