배열
JS에서는 변경 가능하지만, state로 저장할 때에는 변경할 수 없도록 처리해야함.
객체랑 마찬가지로, state에 저장된 배열을 업데이트하고 싶을 땐, 새 배열을 생성한 뒤 이를 이용하여 업데이트해야함.
학습내용
- React state에서 배열의 항목을 추가, 삭제 또는 변경하는 방법
- 배열 내부의 객체를 업데이트하는 방법
- Immer로 덜 반복해서 배열을 복사하는 방법
변경하지 않고 배열 업데이트하기
JS에서 배열은 객체의 다른 종류임.
객체처럼 React state에서는 배열도 읽기 전용이 되어야함.
재할당 이런거 하지마셈 (arr[0] = 'test' / push() / pop())
왜 state 변경 권장하지 않을까 ?
1. 디버깅 힘듦, 렌더링 사이에 state가 어떻게 바뀌었는지 명확하게 알 수 없음
2. 최적화, 리액트 전략이 이전 props / state 가 다음 것과 같을 때 일을 건너뜀
3. 리액트 개발 방향성, 리액트 새 기능을 사용 못 할 수 있음
4. 요구사항 변화, 복원 구현이 힘듦
5. 간단한 구현, 리액트는 변경에 의존하지 않음
state 설정 함수에 새 배열을 넣어서 전달하자.
Immer 쓰면 간단하게 된다고 한다.
slice랑 splice 함수는 이름이 비슷하지만 몹시 다름
splice를 사용하면 배열 또는 그 일부를 복사함
splice는 배열을 변경함 (항목을 추가하거나 제거)
slice를 잘 사용하자
배열에 항목 추가하기
push() 쓰지마셈
스프레드 연산자 쓰셈
// 뒤에 넣을 때
setArtists( // 아래의 새로운 배열로 state를 변경합니다.
[
...artists, // 기존 배열의 모든 항목에,
{ id: nextId++, name: name } // 마지막에 새 항목을 추가합니다.
]
);
// 앞에 넣을 때
setArtists([
{ id: nextId++, name: name }, // 추가할 항목을 앞에 배치하고,
...artists // 기존 배열의 항목들을 뒤에 배치합니다.
]);
배열에서 항목 제거하기
filter 쓰셈.
setArtists(
artists.filter(a => a.id !== artist.id)
);
새 배열을 반환하지 원본 배열을 수정하지 않음
배열 변환하기 / 교체하기
map 쓰셈
새 배열을 반환하지 원본 배열을 수정하지 않음
배열에 항목 삽입하기
slice 쓰셈
setArtists([
...artists.slice(0, insertAt),
{ id: nextId++, name: name },
...artists.slice(insertAt)
])
배열에 기타 변경 적용하기
새 배열을 반환하는게 아닌 원본 바꾸는 메소드의 경우엔 스프레드 연산자로 배열을 복사해서 사용하자
단, 이는 얕은 복사이기 때문에 배열 내부 객체에 접근하지말자.
const nextList = [...list];
nextList[0].seen = true; // 문제 : list[0]을 변경시킴 ;;
setList(nextList);
배열 내부의 객체 업데이트하기
배열 내부의 객체는 주소값임
그래서 중첩된 state를 업데이트할 때, 업데이트하려는 지점부터 최상위 레벨까지의 복사본을 만들어야 한다.
이거는 링크에서 예제 보면서 얘기해보자
Immer로 간결한 업데이트 로직 작성하기
이거도 링크에서 예제 보면서 얘기하자
참 좋네
챌린지
1번 (map 사용)
function handleIncreaseClick(productId) {
setProducts(products.map(product => {
if (product.id === productId) {
return {...product, count: product.count + 1};
}
return product;
}))
}
2번 (map 사용)
function handleDecreaseClick(productId) {
setProducts(products.map(product => {
if (product.id === productId) {
return {
...product,
count: product.count - 1
};
} else {
return product;
}
}))
}
3번, 아쒸 문제 진짜 많네
// TODO 추가
function handleAddTodo(title) {
setTodos([...todos, {
id: nextId++,
title: title,
done: false
}]);
}
// TODO 제거
function handleDeleteTodo(todoId) {
setTodos(todos.filter(todo => todo.id === todoId));
}
// TODO 수정
function handleChangeTodo(nextTodo) {
setTodos(todos.map(todo => {
if (todo.id === nextTodo.id) {
return nextTodo;
}
return todo;
}))
}
4번 (근데 써보니 Immer 쓰기 싫어짐 ;; 뭔가 원본 바꾸는 개념 자체를 가지기 싫은데, 계속 생각나는게 인지부조화가 들어서 싫음)
import { useState } from 'react';
import { useImmer } from 'use-immer';
import AddTodo from './AddTodo.js';
import TaskList from './TaskList.js';
let nextId = 3;
const initialTodos = [
{ id: 0, title: 'Buy milk', done: true },
{ id: 1, title: 'Eat tacos', done: false },
{ id: 2, title: 'Brew tea', done: false },
];
export default function TaskApp() {
const [todos, setTodos] = useImmer(
initialTodos
);
function handleAddTodo(title) {
// 계속 이래서 틀림 ;;
// setTodos(draft => draft.push({id: nextId++, title: title, done: false});
setTodos(draft => {draft.push({
id: nextId++,
title: title,
done: false
})});
}
function handleChangeTodo(nextTodo) {
setTodos(draft => {
const todo = draft.find(t =>
t.id === nextTodo.id
);
todo.title = nextTodo.title;
todo.done = nextTodo.done;
})
}
function handleDeleteTodo(todoId) {
setTodos(draft => {
const index = draft.findIndex(t => t.id === todoId);
draft.splice(index, 1);
})
}
return (
<>
<AddTodo
onAddTodo={handleAddTodo}
/>
<TaskList
todos={todos}
onChangeTodo={handleChangeTodo}
onDeleteTodo={handleDeleteTodo}
/>
</>
);
}
Immer 쓰지말자 ;
'Web + APP > React' 카테고리의 다른 글
[리액트 스터디] - Choosing the State Structure (0) | 2024.07.27 |
---|---|
[리액트 스터디] - Reacting to Input with State (0) | 2024.07.27 |
[리액트 스터디] - Queueing a Series of State Updates (0) | 2024.07.18 |
[리액트 스터디] - State as a Snapshot (2) | 2024.07.18 |
Flux 패턴 (0) | 2021.03.19 |