https://ko.react.dev/reference/react/useReducer
useReducer – React
The library for web and native user interfaces
ko.react.dev
useReducer
useState와 같이 데이터의 상태 관리를 도와주는 훅이다.
useState와의 차이점은
1. 상태 관리 코드를 컴포넌트 외부에서 관리할 수 있다.
2. 상태 변화에 대한 작업 코드를 직접 작성할 수 있다.
const [state, dispatch] = useReducer(reducer, <초기값>);
기본적인 구조이다.
state
상태 관리의 대상이 되는 데이터(변수)이다.
dispatch
상태 변화를 요청하는 함수이다.
인자로 객체를 넘겨주며 객체 안에는 어떤 작업을 수행할 것인지에 대한 프로퍼티 값이 담긴다.
해당 객체를 액션 객체 라고 부른다.
reducer
상태를 변화시키는 함수이다. 새로운 state 값을 반환하여 값을 갱신해준다.
함수의 파라미터로는 state값과 action 값을 받는다.
function reducer(state, action){
if(action.type === 'INCREASE'){
return state + action.data;
}
}
const Component = () => {
const [state, dispatch] = useReducer(reducer, 0);
const onClick =() => {
dispatch({
type: 'INCREASE',
data: 1
});
};
.
.
.
}
onClick 이벤트가 발생하면 dispatch 함수가 호출된다.
dispatch 함수는 상태 변화를 요청하여 reducer 함수가 수행되고, 이때 state 값과 action 객체를 파라미터로 지니게 된다.
useState로 관리하던 상태를 useReducer로 관리해보기
import './App.css';
import { useState } from 'react';
import Editor from './components/Editor/Editor';
import Header from './components/Header/Header';
import List from './components/List/List';
export interface Todo{
id: number;
todo: string;
date: string;
isChecked: boolean;
}
function App() {
const [values, setValues] = useState<Todo[]>([]);
const onCreate = (newValues: Todo) => {
setValues([...values, newValues]);
};
const onUpdate = (targetId: number) => {
setValues(
values.map((value) =>
value.id === targetId
? {...value, isChecked: !value.isChecked}
: value
)
);
};
const onDelete = (targetId: number) => {
setValues(values.filter((value) => value.id!== targetId));
};
return (
<div className='Wrapper'>
<Header />
<Editor onCreate={onCreate}/>
<List values={values} onUpdate={onUpdate} onDelete={onDelete}/>
</div>
);
}
export default App;
현재 values 라는 state에 대해 create, udpate, delete 라는 3가지 상태 변화가 일어나고 있다.
이를 reducer를 통해 관리해 보자.
먼저 액션 객체에 대한 타입을 설정해줬다.
type Action =
| { type: 'CREATE'; todoData: Todo }
| { type: 'UPDATE'; id: number }
| { type: 'DELETE'; id: number };
유니언 타입으로 설정해서 액션 객체는 3가지 형태 중 하나를 타입으로 갖는다.
const reducer = (todos: Todo[], action: Action) => {
switch(action.type) {
case 'CREATE':
return [...todos, action.todoData];
case 'UPDATE':
return todos.map((todo) =>
todo.id === action.id
? {...todo, isChecked: !todo.isChecked}
: todo
);
case 'DELETE':
return todos.filter((todo) => todo.id!== action.id);
}
};
reducer 함수이다. 액션이 여러개이기 때문에 switch문으로 작성해주었고, 각 액션마다 기존 상태 변화 함수의 코드를 작성해주었다.
useState 에서는 set 함수를 사용하였지만, useReducer는 값을 바로 반환해주면 상태가 업데이트된다.
이후 각 함수에 dispatch 함수를 호출해준다.
const onCreate = (newValues: Todo) => {
dispatch({
type: 'CREATE',
todoData: newValues
});
};
이제 onCreate 함수가 호출되면 이어 dispatch 함수를 호출하게 되고, reducer 함수가 동작하여 상태가 변화된다.
전체 코드
import './App.css';
import { useReducer } from 'react';
import Editor from './components/Editor/Editor';
import Header from './components/Header/Header';
import List from './components/List/List';
export interface Todo{
id: number;
todo: string;
date: string;
isChecked: boolean;
}
type Action =
| { type: 'CREATE'; todoData: Todo }
| { type: 'UPDATE'; id: number }
| { type: 'DELETE'; id: number };
const reducer = (todos: Todo[], action: Action) => {
switch(action.type) {
case 'CREATE':
return [...todos, action.todoData];
case 'UPDATE':
return todos.map((todo) =>
todo.id === action.id
? {...todo, isChecked: !todo.isChecked}
: todo
);
case 'DELETE':
return todos.filter((todo) => todo.id!== action.id);
}
};
function App() {
const [todos, dispatch] = useReducer(reducer, []);
const onCreate = (newValues: Todo) => {
dispatch({
type: 'CREATE',
todoData: newValues
});
};
const onUpdate = (targetId: number) => {
dispatch({
type: 'UPDATE',
id: targetId
});
};
const onDelete = (targetId: number) => {
dispatch({
type: 'DELETE',
id: targetId
});
};
return (
<div className='Wrapper'>
<Header />
<Editor onCreate={onCreate}/>
<List values={todos} onUpdate={onUpdate} onDelete={onDelete}/>
</div>
);
}
export default App;
useState와 useReducer가 하는 역할은 동일하지만, 상태가 더욱 복잡해질 수록 useReducer를 통해 관리하는 것이 가독성도 좋고 유지보수에도 용이한 것 같다.
특히 타입스크립트의 경우 액션 객체의 타입을 한번에 관리하여 각 함수의 기능을 파악할 수 있다는 점이 장점이라 생각했다.
그렇지만 useState 역시 러닝커브 없이 편하게 사용할 수 있으며, 그렇게 큰 프로젝트가 아닐 경우 useState로도 충분히 좋은 코드를 짤 수 있다.
각 프로젝트와 팀에 맞는 기술을 선택하는 것이 중요하다는 글을 자주 보는데,,, 참 중요하면서도 어려운 일인 것 같다.
참고)
'웹 > React' 카테고리의 다른 글
[React/리액트] 퍼널(Funnel)구조로 연속되는 화면 구현하기 (2) | 2024.09.07 |
---|---|
[React/리액트] TypeScript 에서 props 넘겨주기 (0) | 2024.09.05 |
[React/리액트] 객체(배열)를 복사하고 부분만 업데이트하기 (0) | 2024.07.26 |
[React/리액트] form 태그와 input 값 다루기 (0) | 2024.07.19 |
[React/리액트] 배열/반복되는 값 렌더링하기 (0) | 2024.07.18 |
https://ko.react.dev/reference/react/useReducer
useReducer – React
The library for web and native user interfaces
ko.react.dev
useReducer
useState와 같이 데이터의 상태 관리를 도와주는 훅이다.
useState와의 차이점은
1. 상태 관리 코드를 컴포넌트 외부에서 관리할 수 있다.
2. 상태 변화에 대한 작업 코드를 직접 작성할 수 있다.
const [state, dispatch] = useReducer(reducer, <초기값>);
기본적인 구조이다.
state
상태 관리의 대상이 되는 데이터(변수)이다.
dispatch
상태 변화를 요청하는 함수이다.
인자로 객체를 넘겨주며 객체 안에는 어떤 작업을 수행할 것인지에 대한 프로퍼티 값이 담긴다.
해당 객체를 액션 객체 라고 부른다.
reducer
상태를 변화시키는 함수이다. 새로운 state 값을 반환하여 값을 갱신해준다.
함수의 파라미터로는 state값과 action 값을 받는다.
function reducer(state, action){
if(action.type === 'INCREASE'){
return state + action.data;
}
}
const Component = () => {
const [state, dispatch] = useReducer(reducer, 0);
const onClick =() => {
dispatch({
type: 'INCREASE',
data: 1
});
};
.
.
.
}
onClick 이벤트가 발생하면 dispatch 함수가 호출된다.
dispatch 함수는 상태 변화를 요청하여 reducer 함수가 수행되고, 이때 state 값과 action 객체를 파라미터로 지니게 된다.
useState로 관리하던 상태를 useReducer로 관리해보기
import './App.css';
import { useState } from 'react';
import Editor from './components/Editor/Editor';
import Header from './components/Header/Header';
import List from './components/List/List';
export interface Todo{
id: number;
todo: string;
date: string;
isChecked: boolean;
}
function App() {
const [values, setValues] = useState<Todo[]>([]);
const onCreate = (newValues: Todo) => {
setValues([...values, newValues]);
};
const onUpdate = (targetId: number) => {
setValues(
values.map((value) =>
value.id === targetId
? {...value, isChecked: !value.isChecked}
: value
)
);
};
const onDelete = (targetId: number) => {
setValues(values.filter((value) => value.id!== targetId));
};
return (
<div className='Wrapper'>
<Header />
<Editor onCreate={onCreate}/>
<List values={values} onUpdate={onUpdate} onDelete={onDelete}/>
</div>
);
}
export default App;
현재 values 라는 state에 대해 create, udpate, delete 라는 3가지 상태 변화가 일어나고 있다.
이를 reducer를 통해 관리해 보자.
먼저 액션 객체에 대한 타입을 설정해줬다.
type Action =
| { type: 'CREATE'; todoData: Todo }
| { type: 'UPDATE'; id: number }
| { type: 'DELETE'; id: number };
유니언 타입으로 설정해서 액션 객체는 3가지 형태 중 하나를 타입으로 갖는다.
const reducer = (todos: Todo[], action: Action) => {
switch(action.type) {
case 'CREATE':
return [...todos, action.todoData];
case 'UPDATE':
return todos.map((todo) =>
todo.id === action.id
? {...todo, isChecked: !todo.isChecked}
: todo
);
case 'DELETE':
return todos.filter((todo) => todo.id!== action.id);
}
};
reducer 함수이다. 액션이 여러개이기 때문에 switch문으로 작성해주었고, 각 액션마다 기존 상태 변화 함수의 코드를 작성해주었다.
useState 에서는 set 함수를 사용하였지만, useReducer는 값을 바로 반환해주면 상태가 업데이트된다.
이후 각 함수에 dispatch 함수를 호출해준다.
const onCreate = (newValues: Todo) => {
dispatch({
type: 'CREATE',
todoData: newValues
});
};
이제 onCreate 함수가 호출되면 이어 dispatch 함수를 호출하게 되고, reducer 함수가 동작하여 상태가 변화된다.
전체 코드
import './App.css';
import { useReducer } from 'react';
import Editor from './components/Editor/Editor';
import Header from './components/Header/Header';
import List from './components/List/List';
export interface Todo{
id: number;
todo: string;
date: string;
isChecked: boolean;
}
type Action =
| { type: 'CREATE'; todoData: Todo }
| { type: 'UPDATE'; id: number }
| { type: 'DELETE'; id: number };
const reducer = (todos: Todo[], action: Action) => {
switch(action.type) {
case 'CREATE':
return [...todos, action.todoData];
case 'UPDATE':
return todos.map((todo) =>
todo.id === action.id
? {...todo, isChecked: !todo.isChecked}
: todo
);
case 'DELETE':
return todos.filter((todo) => todo.id!== action.id);
}
};
function App() {
const [todos, dispatch] = useReducer(reducer, []);
const onCreate = (newValues: Todo) => {
dispatch({
type: 'CREATE',
todoData: newValues
});
};
const onUpdate = (targetId: number) => {
dispatch({
type: 'UPDATE',
id: targetId
});
};
const onDelete = (targetId: number) => {
dispatch({
type: 'DELETE',
id: targetId
});
};
return (
<div className='Wrapper'>
<Header />
<Editor onCreate={onCreate}/>
<List values={todos} onUpdate={onUpdate} onDelete={onDelete}/>
</div>
);
}
export default App;
useState와 useReducer가 하는 역할은 동일하지만, 상태가 더욱 복잡해질 수록 useReducer를 통해 관리하는 것이 가독성도 좋고 유지보수에도 용이한 것 같다.
특히 타입스크립트의 경우 액션 객체의 타입을 한번에 관리하여 각 함수의 기능을 파악할 수 있다는 점이 장점이라 생각했다.
그렇지만 useState 역시 러닝커브 없이 편하게 사용할 수 있으며, 그렇게 큰 프로젝트가 아닐 경우 useState로도 충분히 좋은 코드를 짤 수 있다.
각 프로젝트와 팀에 맞는 기술을 선택하는 것이 중요하다는 글을 자주 보는데,,, 참 중요하면서도 어려운 일인 것 같다.
참고)
'웹 > React' 카테고리의 다른 글
[React/리액트] 퍼널(Funnel)구조로 연속되는 화면 구현하기 (2) | 2024.09.07 |
---|---|
[React/리액트] TypeScript 에서 props 넘겨주기 (0) | 2024.09.05 |
[React/리액트] 객체(배열)를 복사하고 부분만 업데이트하기 (0) | 2024.07.26 |
[React/리액트] form 태그와 input 값 다루기 (0) | 2024.07.19 |
[React/리액트] 배열/반복되는 값 렌더링하기 (0) | 2024.07.18 |