일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 익스프레스
- 리액트
- js코테
- 코딩테스트
- 백준구현
- 백준구현문제
- HTML5
- 프로그래머스
- 포이마웹
- 프로그래머스JS
- JS프로그래머스
- 다이나믹프로그래밍
- 프로그래머스코테
- 리액트커뮤니티
- HTML
- 백준알고리즘
- CSS
- 리액트댓글기능
- 백준
- 백준js
- 자바스크립트
- css기초
- dp알고리즘
- 코테
- 백준골드
- JS
- 안드로이드 스튜디오
- 백준nodejs
- 알고리즘
- 몽고DB
- Today
- Total
개발새발 로그
React - useReducer에 대하여 본문
useReducer
이제까지 state를 생성하고 관리하는 useState를 사용했다.
하지만 리액트에서 state를 관리하기 위한 hook이 또 있는데
그것이 바로 useReducer가 있다.
useState처럼 state를 생성하고 관리한다.
대체 언제 사용할까?
여러개의 하위 값을 포함하는 복잡한 state를 다뤄야할 때
useState대신 useReducer를 사용하면 코드를 깔끔하게 쓸 수 있고, 유지 보수도 편리해진다.
useReducer는 Reducer, Dispatch, Action 3가지로 이루어져있다.
사용자가 state를 변경하려고 Reducer에게 요구할 때 -> Dispatch
사용자가 state를 어떻게 변경하려는지 요구안에 그 내용을 담는다. -> Action
Reducer는 해당 요구를 받아서 state를 변경한다.
코드로 보면 아래와 같다.
Dispatch(Action) ---> Reduecer(State, Action) ---> state 변경
사용방법
const [state, dispatch] = useReducer(reducer, initialArg, init?)
반환 값
1. state : 현재 상태입니다. 첫 번째 렌더링 시에는 init(initialArg ) 또는 초기화되지 않은 경우 initialArg로 설정됩니다.
2. dispatch : 상태를 다른 값으로 업데이트하고 렌더링을 다시 트리거할 수 있는 디스패치 함수입니다.
매개변수
1. redeucer : 상태가 업데이트되는 방식을 지정하는 reducer 함수입니다.
2. initialArg : 초기 상태가 계산되는 값입니다. 모든 유형의 값이 될 수 있습니다. 초기 상태가 계산되는 방식은 다음 init 인수에 따라 달라집니다.
3. option init? : 초기 상태를 반환해야 하는 이니셜라이저 함수입니다. 지정하지 않으면 초기 상태가 initialArg로 설정됩니다. 그렇지 않으면 초기 상태는 init(initialArg)를 호출한 결과로 설정됩니다.
reducer함수 생성
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
실제 사용 방법
import { useReducer } from 'react';
// reducer함수
function reducer(state, action) {
//type 값에 따라 분기처리
if (action.type === 'incremented_age') {
return {
age: state.age + 1
};
}
throw Error('Unknown action.');
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
return (
<>
<button onClick={() => {
dispatch({ type: 'incremented_age' })// type을 dispatch
}}>
Increment age
</button>
<p>Hello! You are {state.age}.</p>
</>
);
}
보통은 아래와 같이 switch문을 활용한다.
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Unknown action: ' + action.type);
}
액션은 어떤 형태든 가질 수 있습니다.
function Form() {
const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
function handleButtonClick() {
dispatch({ type: 'incremented_age' });
}
function handleInputChange(e) {
dispatch({
type: 'changed_name',
nextName: e.target.value
});
}
// ...
일반적으로 액션을 식별하는 type 속성을 가진 객체를 전달하는 것이 일반적입니다.
여기에는 reducer가 다음 상태를 계산하는 데 필요한 최소한의 필수 정보가 포함되어야 합니다.
주의점!
상태는 읽기 전용입니다. 상태의 개체나 배열을 수정하지 마세요
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// 🚩 이와 같은 상태의 개체는 변경하지 마세요:
state.age = state.age + 1;
return state;
}
대신 항상 reducer에서 새 객체를 반환하세요
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ✅ 대신 새 객체를 반환합니다.
return {
...state,
age: state.age + 1
};
}
자세한 내용은 상태 오브젝트 업데이트하기 및 상태 배열 업데이트하기를 참조하세요.
사실 위 코드들은 너무 간단해서 useReducer보단 useState를 쓰는 것이 더 낫다.
하지만 복잡한 상태를 관리할 떄는 useReducer를 쓰면 더욱 효과적일 것이다.
아래 예시를 보자
아래 코드는 최상위 컴포넌트에서 reducer가 어떻게 사용되었는지의 예시다.
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
export default function TaskApp() {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId
});
}
return (
<>
<h1>Prague itinerary</h1>
<AddTask
onAddTask={handleAddTask}
/>
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
let nextId = 3;
const initialTasks = [
{ id: 0, text: 'Visit Kafka Museum', done: true },
{ id: 1, text: 'Watch a puppet show', done: false },
{ id: 2, text: 'Lennon Wall pic', done: false }
];
위 처럼 복잡한 state를 관리해야할 때 코드를 깔끔하게 작성할 수 있다.
초기상태를 만드는 함수를 최적화하기
React는 보통 초기 상태를 한 번 저장하고 다음 렌더링에서 이를 무시합니다.
하지만 아래 코드에서 createInitialState(username) 의 결과는 초기 렌더링에만 사용되지만,
여전히 모든 렌더링에서 이 함수를 호출하게 됩니다.
이는 큰 배열을 만들거나 값비싼 계산을 수행하는 경우 낭비가 될 수 있습니다.
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
// ...
이 문제를 해결하려면 이니셜라이저 함수로 전달하여 대신 세 번째 인수로 Reducer를 사용할 수 있습니다
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...
함수를 호출한 결과인 createInitialState()가 아니라 함수 자체인 createInitialState를 전달하고 있다는 점을 봐야합니다.
이렇게 하면 초기화 후 초기 상태가 다시 생성되지 않습니다.
위의 예에서 createInitialState는 usename을 인수를 받습니다..(두 번째 인수)
이니셜라이저가 초기 상태를 계산하는 데 아무런 정보가 필요하지 않은 경우,
useReducer의 두 번째 인수로 null을 전달할 수 있습니다.
'React' 카테고리의 다른 글
React - @vitejs/plugin-react' 모듈 또는 해당 형식 선언을 찾을 수 없습니다. (0) | 2023.12.15 |
---|---|
React - 컴포넌트 심화 (0) | 2023.12.15 |
React - useCallback에 대하여 (0) | 2023.12.13 |
React - 커스텀 훅을 만들면서 (0) | 2023.12.12 |
React - useMemo에 대하여 (1) | 2023.12.11 |