React로 개발할 때면 항상 Hook의 다양성을 몰라 개발의 효율성이 떨어지는 걸 느낄 수 있었습니다..
그래서,
React Hook을 파헤쳐보고 알아가면서 프로젝트를 진행하고 있습니다! 그와 동시에 알아가면 좋은(거의 모든) hook들을 공부하며 블로그에 정리해보려고 합니다.
오늘은 useReducer라는 것을 알아보겠습니다.
React Hook이란,
React 컴포넌트에서 계속 발전되고 업데이트 되고 있는 다양한 React 기능을 사용할 수 있습니다. 내장된 Hook을 이용하거나 이를 결합하여 자신만의 Hook을 만들 수 있습니다.
React Hook은 종류가 다양해서 알면 알 수록 신기하고 프로젝트의 효율성을 더 높힐 수 있을 거라고 생각합니다.
종류는 다양한데 대표 Hook 들입니다.
1. State Hooks : 사용자 입력과 같은 정보를 기억하는 Hook
2. Context Hooks : 너무 멀리 있는 컴포넌트로 props를 보내지 않아도 정보 전송이 되는 Hook
3. Ref Hooks : 렌더리엥 사용되지 않는 일부 정보를 보유하는 Hook
4. Effect Hooks : 외부 시스템에 연결하고 동기화할 수 있는 Hook
5. Performance Hooks : 재렌더링 성능을 최적화하는 방법으로 데이터가 변경되지 않으면 건너뛰는 Hook
와 같이 있습니다. 저는 가장 많이 사용했던게 State Hook과 Effect Hook이랍니다.
지금 개발중인 프로젝트에서는 Context Hooks도 사용중입니다! Ref Hooks과 Performance Hooks은 사용해보고 싶네요..!
https://ko.react.dev/reference/react/hooks
useReducer
본격적으로 useReducer에 대해 알아보겠습니다.
https://ko.react.dev/reference/react/useReducer
한 컴포넌트 안에서 사용하면 useState로 사용하지만 다른 컴포넌트에도 값을 전달해야하는 경우, 그 다른 컴포넌트가 너무 상위, 하위 컴포넌트 즉 거리가 먼 컴포넌트에 전달해야하는 경우에 사용하는게 useReducer입니다.(Redux와 비슷하답니다)
즉 useState의 상위 버전의 상태 관리 Hook입니다.
useState : set을 통해 값을 변경하는 방법
useReducer : actiod을 dispatch 함수로 전달한 뒤 지정된 함수를 통해 값 변경하는 방법
액션을 통해 상태가 변화하며, 액션의 타입과 내용을 기반으로 리듀서 함수가 상태를 업데이트하는 방식입니다.
useReducer 사용법(예제)
1. state를 설정하는 것에서 action을 dispatch 함수로 전달하는 것으로 바꾸기
2. reducer 함수 작성하기
3. 컴포넌트에서 reducer 사용하기
위 세가지 단계로 만들어주시면 됩니다.
3단계로 알려주는 것도 좋지만 코드 특성상 선언 -> 함수 -> 호출 -> 리턴값 -> 완료 식으로 알려드리겠습니다.
1. 기본 문법
const [state, dispatch] = useReducer(reducer, initialState);
useReducer를 선언하는 방법입니다.
state : 현재 상태
dispatch : 액션을 디스패치하여 상태를 업데이트할 수 있는 함수
reducer : 상태를 업데이트하는 함수
initialState : 상태의 초기값
** dispatch : 보내다, reducer : 줄이다,변경하다, initialState : 초기상태
useReducer에서 가장 중요한 것은 action이라고 생각합니다.
action은 값을 변경할 때 보내는 것으로 "사용자가 방금 한일"을 지정합니다. action 객체는 어떤 형태든 될 수 있습니다. action의 타입을 넘겨주고 이외의 정보는 다른 필드에 담아서 전달하도록 작성하는 게 일반적입니다.
2. reducer 함수 작성하기
redcuer 함수는 state에 대한 로직을 넣는 곳입니다. 이 함수는 현재의 state값과 action 객체, 이렇게 두개의 인자를 받고 다음 state 값을 반환합니다.
function tasksReducer(tasks, action) {
if (action.type === 'added') { // 추가
return [...tasks, { // ...이전 값, { 추가 값}
id: action.id,
text: action.text,
done: false
}];
} else if (action.type === 'changed') { // 변경
return tasks.map(t => {
if (t.id === action.task.id) { // action 내부의 task의 아이디로 구분하고 값 변경
return action.task;
} else {
return t;
}
});
} else if (action.type === 'deleted') { // 삭제
return tasks.filter(t => t.id !== action.id); // tasks에서 action의 task id와 다른 것만 filtering
} else {
throw Error('Unknown action: ' + action.type);
}
}
위와 같이 react 공식 문서에 예시로 작성된 코드입니다.
action의 type을 확인하면서 값을 변경해줍니다.
3. 컴포넌트에서 reducer 사용하기
import { useReducer } from 'react';
react hook인 useReducer를 호출합니다.
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
useReducer의 값을 선언합니다.(useState와 비슷합니다)
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
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}
/>
</>
);
}
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);
}
}
}
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 }
];
React 공식 문서에 있는 예제 코드입니다.
실제 함수로 알려드리겠습니다.
저는 useReducer를 사용하는 경우를 지정해 두었습니다.
- 진행중인 page값
- 로그인한 사용자(간단 정보) - 로그인 실제 정보는 cache저장할 예정입니다.
- sidebar 색 저장(예제로만 지정해뒀고 우선순위는 마지막)
- config 설정 바 열려있는지 여부 확인 값 (true/false)
- 사이드바 열려있는 지 여부 확인값 (true/false)
이정도로 생각중입니다.
그 외로 중요한 데이터는 redux를 사용할 예정입니다.
아, 여기에서 궁금증이 생길 수 있는 데 redux와 useReducer랑 같지 않나?라고 생각할 수 있습니다. 물론 저도 궁금해서 찾아봤습니다.
useReducer와 Redux의 차이점
기본적으로 redux는 설치해야 해서 설치 여부로 차이점이 있습니다. 그리고 redux는 전역 상태 관리가 가능하여 전역에서 컴포넌트 별로 값을 전달할 수 있습니다. 또한 확장 기능도 있어 비동기 작업이나 로깅, 상태 변경 모니터링 등 다양한 기능을 쉽게 추가할 수 있습니다. 디버깅과 개발자 도구 DevTools를 통해 상태의 변화를 추적하고 디버깅하기 쉽게 지원합니다. 마지막으로 중앙 스토어(Store)에서 모든 상태를 관리하고, 컴포넌트가 상태를 구독하거나 액션을 디스패치하여 상태를 변경합니다.
useReducer와 다른 Redux의 특징
- 설치여부
- 전역 상태 관리 가능
- 확장 기능이 있음
- 디버깅과 개발자 도구 편리성
- 중앙 스토어로 모든 상태 관리
일단 주 목적이 아니니까 간단하게 설명 드렸습니다.
마무리 (실제 코드 feat. typescript)
useReducer는 한 컴포넌트에서 state 업데이트가 여러 이벤트 핸들러로 분산되는 경우일 때 관리가 어려운 것을 보완하는 React hook입니다.
즉, 상태를 관리하는 게 useReducer입니다.
실제 구현한 코드로 알려드릴게요. 저는 Typescript로 구현했고 context도 있습니다.
import React, { createContext, useReducer, useContext, ReactNode } from "react";
// 상태 타입 정의
interface StateType {
page: string;
user?: string;
sidenavColor: string;
openConfigurator: boolean;
fixedNavbar: boolean;
}
// 액션 타입 정의
type ActionType =
| { type: "SET_PAGE"; payload: { page: string; user?: string } }
| { type: "SIDENAV_COLOR"; payload: { sidenavColor: string } }
| { type: "CHANGE_CONFIG_STATE"; payload: { stateConfigurator: boolean } }
| { type: "FIXED_NAVBAR"; payload: { fixedNavbar: boolean } };
// 초기 상태 정의
const initialState: StateType = {
page: "dashboard",
user: "user",
sidenavColor: "pink",
openConfigurator: true,
fixedNavbar: true
};
// 리듀서 함수 정의
function reducer(state: StateType, action: ActionType): StateType {
switch (action.type) {
case "SET_PAGE":
return {
...state,
page: action.payload.page,
user: action.payload.user ?? state.user,
};
case "SIDENAV_COLOR":
return {
...state,
sidenavColor: action.payload.sidenavColor,
};
case "CHANGE_CONFIG_STATE":
return {
...state,
openConfigurator: action.payload.stateConfigurator,
};
case "FIXED_NAVBAR":
return {
...state,
fixedNavbar: action.payload.fixedNavbar,
};
default:
throw Error;
}
}
// 컨텍스트 생성
const PageContext = createContext<{
state: StateType;
dispatch: React.Dispatch<ActionType>;
} | undefined>(undefined);
// 컨텍스트 프로바이더 생성
function PageProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<PageContext.Provider value={{ state, dispatch }}>
{children}
</PageContext.Provider>
);
}
// 커스텀 훅
function usePageContext() {
const context = useContext(PageContext);
if (context === undefined) {
throw new Error("usePageContext must be used within a PageProvider");
}
return context;
}
// 액션 생성자 함수
const setPage = (dispatch: React.Dispatch<ActionType>, page: string, user?: string) => {
dispatch({ type: "SET_PAGE", payload: { page, user } });
};
const setSidenavColor = (dispatch: React.Dispatch<ActionType>, sidenavColor: string) => {
dispatch({ type: "SIDENAV_COLOR", payload: { sidenavColor } });
};
const setOpenConfigurator = (dispatch: React.Dispatch<ActionType>, stateConfigurator: boolean) => {
dispatch({ type: "CHANGE_CONFIG_STATE", payload: { stateConfigurator } });
};
const setFixedNavbar = (dispatch: React.Dispatch<ActionType>, fixedNavbar: boolean) => {
dispatch({ type: "FIXED_NAVBAR", payload: { fixedNavbar } })
};
export {
PageProvider,
usePageContext,
setPage,
setSidenavColor,
setOpenConfigurator,
setFixedNavbar
};
해당 값을 호출하는 페이지에서는 필요한 것들만 호출하면 됩니다.
import {usePageContext, setPage} from 'context'
...
...
// reducer로 값 가져오기
const { state, dispatch } = usePageContext();
useEffect(() => {
console.log("현재페이지" ,collapseName)
// reducer로 지정
setPage(dispatch, collapseName)
// console.log("reducer 사용 후 컨트롤러", state)
}, [location]);
'프론트 > React Native, React' 카테고리의 다른 글
[React] 리액트 form 태그 라이브러리 사용하여 회원가입 구현 react-hook-form 사용하기 (3) | 2024.11.24 |
---|---|
[오픈소스] React 프로젝트에서 사용할 만한 오픈소스와 라이브러리 10가지 (18) | 2024.11.13 |
[리액트] 리액트(React)의 사용 이유와 장단점 (2) | 2023.10.10 |
[리액트 네이티브] non-std C++ exception error 해결하는 방법 (0) | 2023.08.09 |
[리액트네이티브] react-native 체크 박스 만들기 (Expo) (0) | 2023.07.09 |