Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |
Tags
- 코드잇
- 방송대
- TiL
- presignedurl
- Cookie
- CSS
- 유노코딩
- 코딩테스트
- 99클럽
- Python
- 파이썬프로그래밍기초
- MySQL
- SQL
- 엘리스sw트랙
- 중간이들
- 방송대컴퓨터과학과
- 데이터베이스시스템
- 파이썬
- 꿀단집
- nestjs
- aws
- Git
- 코딩테스트준비
- HTML
- node.js
- JavaScript
- 프로그래머스
- 항해99
- 개발자취업
- redis
Archives
- Today
- Total
배꼽파지 않도록 잘 개발해요
[엘리스sw] 12주차 1일 - 상태 관리 본문
◆ 상태 관리
◆ 상태 관리 기술이 해결하는 문제들
◆ Flux Pattern
◆ useState, useRef, useContext, useReducer
◆ useState를 활용한 상태관리
◆ useContext를 활용한 상태관리
상태 관리
상태 관리
- 상태 관리 기술이란 앱 상에서의 데이터를 메모리(localStorage, JS memory) 등에 저장하고 하나 이상의 컴포넌트에서 데이터를 공유하는 것
- 한 컴포넌트 안에서의 상태, 여러 컴포넌트 간의 상태, 전체 앱의 상태 관리를 모두 포함함.
MPA와 SPA에서의 상태 관리
- MPA에서는 서버의 데이터를 이용해 페이지를 렌더링하므로, 클라리언트의 데이터와 서버의 데이터가 큰 차이를 가지지 않음.
- SPA에서는 자체적으로 데이터를 갖고, 서버와의 동기화가 필요한 데이터만을 처리함.
- 그 외의 데이터는 Client만의 데이터로 유지.
상태관리 기술의 도입
- 상태가 많지 않거나, 유저와의 인터렉션이 많지 않다면 매 작업 시 서버와 동기화하더라도 충분함.
- 앱이 사용하는 데이터가 점점 많아지고, 유저와의 인터렉션 시 임시로 저장하는 데이터가 많아지는 경우 상태 관리를 고려함.
- 프론트엔드 뿐만 아니라, 백엔드와의 데이터 통신도 충분히 고려해야 함.
ex) GraphQL
상태 관리 기술의 장점
- 높은 품질의 코드를 작성하는 데 유리.
- 성능 최적화, 네트워크 최적화 등에 유리.
- 데이터 관리의 고도화.
ex) localStroage 활용한 persist state
상태 관리 기술의 단점
- Boilerplate 문제
- 파악해야 할 로직과 레이어가 많아짐.
- 잘못 사용할 경우, 앱의 복잡도만을 높이거나, 성능을 악화시킴.
ex) global state의 잘못된 활용 시 앱 전체 리렌더링 발생함.
상태 관리 기술이 해결하는 문제들
데이터 캐생과 재활용
- SPA에서 페이지 로딩 시마다 모든 데이터를 로딩한다면, 사용자 경험 측면에서 MAP를 크게 넘어서기 힘듦.
- 오히려 네트워크 요청 수가 많아져 더 느릴 수도 있음.
- 변경이 잦은 데이터가 아니라면, 데이터를 캐싱하고 재활용함.
- 변경이 잦다면, 데이터의 변경 시점을 파악해 최적화.
ex) 일정 시간마다 서버에 저장, 타이핑 5초 후 서버에 저장함.
Prop Drilling
- 컴포넌트가 복잡해지는 경우, 상위 부모와 자식 컴포넌트 간의 깊이가 커짐.
- 최하단의 자식 컴포넌트가 데이터를 쓰기 위해 최상위 컴포넌트부터 데이터를 보내야 하는 상황이 발생함.
- Context API 등을 활용, 필요한 컴포넌트에서 데이터를 가져올 수 있음.
컴포넌트 간의 결합성을 낮춤.
Flux Pattern
Flux Pattern
- 2014년에 Facebook에서 제안한 웹 애플리케이션 아키텍처 패턴.
- Unidirectional data flow를 활용, 데이터의 업데이트와 UI 반영을 단순화.
- React의 UI 패턴인 합성 컴포넌트와 어울리도록 설계.
- redux, react-redux 라이브러리의 Prior art.
MVC Pattern
- MVC Pattern이란 애플리케이션을 Model, View, Controller로 분리하여 개발하는 소프트웨어 디자인 패턴 중 하나.
Flux Pattern vs MVC Pattern
- 하나의 유저 인터렉션이 무조건 하나의 업데이트만 만들 수 있는 것은 아님. 예를 들어 특정 버튼을 클릭했을 때 여러 개의 액션을 만들 수 있기 때문임.
- MVC 패턴의 경우, 하나의 유저 인터랙션 발생 시 그 인터랙션으로 발생한 업데이트가 다른 연쇄 업데이트를 만들어낼 수 있음. 따라서 업데이트의 근원을 추적하기 힘든 반면 Flux 패턴은 연쇄 업데이트가 아닌 단방향 업데이트만을 만들어낼 수 있음.
- Flux는 하나의 Action이 하나의 Update만을 만들도록 함.
Flux 구조
- Action → Dispatcher → Store → View 순으로 데이터가 흐름.
- store은 미리 dispatcher에 callback을 등록해, 자신이 처리할 action을 정의.
- action creator는 action을 생성하여 dispatcher로 보냄.
- dispatcher는 action을 store로 넘김.
- store는 action에 따라 데이터를 업데이트 후, 관련 view로 변경 이벤트 발생.
- View는 그에 따라 데이터를 다시 받아와 새로운 UI를 만듦.
- 유저 인터렉션이 발생하면 View는 action을 발생.
useState, useRef, useContext, useReducer
상태관리에 사용되는 훅
- 외부 라이브러리 없이 React가 제공하는 Hook만으로 상태관리를 구현하기 위해 사용.
- 함수형 컴포넌트에 상태를 두고, 여러 컴포넌트 간 데이터와 데이터 변경 함수를 공유하는 방식으로 상태를 관리하게 됨.
useState
- 단순한 하나의 상태를 관리하기에 적합함.
- const [state, setState] = useState(initState | initFn)
- state가 바뀌면, state를 사용하는 컴포넌트를 리렌더함.
- useEffect와 함께, state에 반영하는 훅을 구축.
useRef
- 상태가 바뀌어도 리렌더링하지 않는 상태를 정의함.
- 즉, 상태가 UI의 변경과 관계없을 때 사용.
ex) setTimeout의 timerld 저장 - uncontrolled component의 상태를 조작하는 등, 리렌더링을 최소화하는 상태관리에 사용됨.
ex) Dynamic Form 예시
useContext
- 컴포넌트와 컴포넌트 간 상태를 공유할 때 사용.
- 부분적인 컴포넌트들의 상태 관리, 전체 앱의 상태 관리를 모두 구현.
- Context Provider 안에서 렌더링되는 컴포넌트는, useContext를 이용해 깊이 nested된 컴포넌트라도 바로 context value를 가져옴.
- context value가 바뀌면 내부 컴포넌트는 모두 리렌더링됨.
useReducer
- useState보다 복잡한 상태를 다룰 때 사용
- 별도의 라이브러리 없이 flux parttern에 기반한 상태 관리를 구현.
- const [state, dispatch] = useReducer(reducer, initState)
- nested state 등 복잡한 여러 개의 상태를 한꺼번에 관리하거나, 어떤 상태에 여러 가지 처리를 적용할 때 유용.
- 상태 복잡하다면, useState에 관한 callback을 내려주는 것보다 dispatch를 prop으로 내려 리렌더링을 최적화하는 것을 권장.
useState를 활용한 상태관리
useState를 활용한 상태 관리
- 상위 컴포넌트에서 state와 state 변경 함수를 정의하고, 그 state나 변경 함수를 사용하는 컴포넌트까지 prop으로 내려주는 패턴.
- state가 변경되면, 중간에 state를 넘기기만 하는 컴포넌트들도 모두 리렌더링됨.
- 상태와 상태에 대한 변화가 단순하거나, 상대적으로 소규모 앱에서 사용하기에 적합.
useState 예시 - TodoApp
TodoApp.jsx
function TodoApp() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState("all");
const [globalId, setGlobalId] = useState(3000);
const toggleTodo = (id) => {
setTodos((todos) =>
todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
const deleteTodo = (id) => {
setTodos((todos) => todos.filter((todo) => todo.id !== id));
}
const addTodo = (title) => {
setTodos((todos) => [{ title, id: globalId + 1}, ...todos]);
setGlobalId((id) => id + 1);
};
return <TodosPage
state={{ todos, filter }}
toggleTodo={toggleTodo}
addTodo={addTodo}
deleteTodo={deleteTodo}
changeFilter={setFilter}
/>
};
TodosPage.jsx
function TodosPage({ state, addTodo, deleteTodo, toggleTodo, changeFilter }) {
const filteredTodos = state.todos.filter((todo) => {
const { filter } = state;
return (
filter === "all" ||
(filter === "completed" && todo.completed) ||
(filter === "todo" && !todo.completed)
);
});
return (
<div>
<h3>TodosPage</h3>
<TodoForm onSubmit={addTodo} />
<TodoFilter filter={state.filter} chagneFilter={changeFilter}/>
<TodoList
todos={filteredTodos}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
</div>
);
}
TodoForm.jsx
function TodoForm ({ onSubmit }) {
const [title, setTitle] = useState("");
return (
<form
onSubmit={(e) => {
e.preventDefault();
onSubmit(title);
setTitle("");
}}
>
<label htmlFor="todo-title">Title</label>
<input id="todo-title"
type="text"
name="todo-title"
onChange={(e) => setTitle(e.target.value)} value={title} />
<button type="submit">Make</button>
</form>
);
}
TodoList.jsx
function TodoList({ todos, toggleTodo, deleteTodo }) {
return (
<ul>
{todos.map(({ title, completed, id}) => (
<li onClick={() => toggleTodo(id)}>
<h5>{title}</h5>
<div>
{completed ? "체크" : "수정"}
<button onClick={() => deleteTodo(id)}>Delete</button>
</div>
</li>
))}
</ul>
);
}
TodoFilter.jsx
function TodoFilter({ filter, changeFilter }) {
return (
<div>
<label htmlFor="filter">Filter</label>
<select
onChange={(e) => changeFilter(e.target.value)}
id="filter"
name="filter"
>
{filterList.map((filterText) => (
<option selected={filter === filterText} value={filterText}>
{capitalize(filterText)}
</option>
))}
</select>
</div>
);
}
useContext를 활용한 상태관리
- Provider 단에서 상태를 정의하고, 직접 상태와 변경 함수를 사용하는 컴포넌트에서 useContext를 이용해 바로 상태를 가져와 사용하는 패턴.
- useReducer와 함께, 복잡한 상태와 상태에 대한 변경 로직을 두 개 이상의 컴포넌트에서 활용하도록 구현 가능.
- state는 필요한 곳에서만 사용하므로, 불필요한 컴포넌트 리렌더링을 방지.
- Prop Drilling(Plumbing)을 방지하여 컴포넌트 간 결합도를 낮춤.
useState 예시 - TodoApp
TodoContext.jsx
const TodoContext = createContext(null);
const initialState = {
todos: [],
filter: "all",
globalId: 3000,
};
function useTodoContext() {
const context = useContext(TodoContext);
if (!context) {
throw new Error("Use TodoContext inside Provider.")
}
return context;
}
function TodoContextProvider({ children }) {
const values = useTodoState();
return <TodoContext.Provider
value={values}>{children}</TodoContext.Provider>;
}
function reducer(state, action) {
switch (action.type) {
case "change filter":
return { ...state, filter: action.payload.filter };
case "init.todos":
return { ...state, todos: action.payload.todos };
case "add.todo":{
return { ...state, todos: [{ title: action.payload.title, id: state.globalId + 1 }], globalId: state.globalId + 1};
}
case "delete.todo": {
return { ...state, todos:
state.todos.filter((todo) => todo.id !== action.payload.id) };
}
case "toggle.todo": {
return { ...state, todos: state.todos.map((t) => t.id === action.payload.id ? { ...t, completed: !t.completed } : t)};
}
default: return state;
}
}
728x90
'교육 > 엘리스 SW 학습 내용' 카테고리의 다른 글
[엘리스sw] 12주차 5일 - React 테스팅 (0) | 2024.03.20 |
---|---|
[엘리스sw] 12주차 3일 - Redux (0) | 2024.03.20 |
[엘리스sw] 11주차 5일 - React의 비동기 통신 (0) | 2024.03.12 |
[엘리스sw] 11주차 3일 - SPA, 라우팅 (0) | 2024.03.12 |
[엘리스sw] 11주차 1일 - React 스타일링 (0) | 2024.03.11 |