상태 관리 기술
이란, 여러 컴포넌트에서 데이터를 공유하는 것을 의미한다.
상태 관리 기술을 도입하면 데이터 관리를 더욱 효율적으로 할 수 있고, 성능과 네트워크를 최적화하는 데 유리하다. 단 잘못 사용한다면 로직이 복잡해지고, 되려 성능이 악화될 수 있다.
MVC 패턴에서는 View에서 데이터의 업데이트가 발생하면 그에 따라 연쇄적으로 업데이트가 발생한다. 즉, MVC 패턴은 데이터의 변경 사항을 신속하게 전파하기가 어렵다. 또한 MVC 패턴은 Model이 View를 반영하고, View가 Model을 변경하는 양방향 데이터 흐름이다.
이런 문제점을 해결하기 위해 Flux Pattern을 사용하면 하나의 Action에 하나의 업데이트가 발생하도록 할 수 있다.
• Flux 데이터의 흐름 순서
Action → Dispatcher → Store → View
⇨ 단방향 흐름
⁕ Flux의 데이터는 단방향 흐름이다.
① 액션 생성자는 새로 발생한 액션의 타입과 데이터 페이로드를 액션 메시지로 묶어 디스패쳐로 전달한다.
② 디스패쳐는 액션 메시지를 감지하고 각 스토어에 전달한다. 전달은 콜백 함수로 이루어지며, 등록되어 있는 모든 스토어로 페이로드를 전달할 수 있다.
③ 스토어는 어플리케이션의 상태와, 상태를 변경할 수 있는 메서드를 가지고 있다. 어떤 타입의 액션이 날아왔느냐에 따라 메서드를 다르게 적용해 상태를 변경하게 됩니다.
④ 컨트롤러 뷰는 스토어에서 변경된 데이터를 가져와 모든 자식 뷰에게 데이터를 분배한다. 데이터를 넘겨받은 뷰는 화면을 새로 렌더링한다.
useState는 state를 다루는 글에서 이미 정리했으니 간단하게만 되짚어보겠다. 🔗
useState
는 단순한 상태를 관리할 때 사용하기 적합하다. state가 바뀌면, 해당 state를 사용하고 있는 컴포넌트가 재렌더링된다.
useState를 정의할 때는 배열 안에 state 변수와 state 갱신 함수를 할당하고 useState로 초기 값을 설정한다.
💡const [state, setState] = useState(initialState)
useRef
는 useState와 달리 ref의 값이 변경되어도 컴포넌트가 재렌더링되지 않는다. 따라서 상태가 바뀌어도 재렌더링이 필요하지 않은, 상태가 UI의 변경과 관계없을 때 사용한다. 상황에 따라 알맞게 useState 대신 useRef를 사용함으로써 재렌더링을 최소화할 수 있다.
useRef 함수는 current
속성을 가지고 있는 ref객체를 반환한다.
따라서, 현재 ref 객체의 상태값을 얻으려면 ref객체.current
로 접근하면 된다.
💡ref = useRef(initialState)
import { useRef } from 'react'
function App() {
// useRef 정의
const inputRef = useRef();
return (
<div className="App">
// ref 속성값으로 ref 객체를 전달하면 input 제어 가능
<input ref={inputRef} type="text"/>
<button onClick={() => {
// 버튼을 누르면 '안녕하세요. input 입력값'을 alert 띄우는 이벤트
alert(`안녕하세요. ${inputRef.current.value}`);
}}>Login</button>
</div>
);
}
useReducer
를 사용하면 useState보다 복잡한 상태값을 관리할 수 있다.
useReducer는 상태인 state
와 reducer 함수에 action을 전달하는 dispatch
함수를 반환한다.
초기값 하나의 인자만 받는 useState와 달리, useReducer는 두 개의 인자를 받는다. 첫 번째로, 발생하는 action에 따라 상태를 관리하는 함수인 reducer
와 두번째로 상대의 초기값이다.
💡const [state, dispatch] = useReducer(reducer, initialState);
dispatch는 상태 변경을 할 때 일으킬 action을 전달 받는다. 이 전달받은 action을 reducer로 전달해주는 함수이다.
⁕ 여기서 action은 발생한 액션의 type과 데이터 payload를 담고 있는 객체이다.
reducer는 state와 action을 넘겨받고 그 action.type에 따른 state를 변경하여 새로운 state를 반환하는 함수이다.
즉, dispatch는 reducer로 행동할 액션을 전달하고
reducer는 dispatch로부터 전달받은 액션에 따라 상태를 변경한다.
import { useReducer } from 'react'
const initialState = {count: 0}; // 초기값 선언
function reducer(state, action) {
switch (action.type) { // action type에 따른 상태 변경 처리
case 'increment': // action type으로 increment가 들어왔을 때
return {count: state.count + 1};
case 'decrement': // action type으로 decrement가 들어왔을 때
return {count: state.count - 1};
default: // 그 외 값이 들어왔을 때(default)
throw new Error();
}
}
// dispatch를 통해 reducer 함수로 사용자 action 전달
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useContext
는 state를 컴포넌트 간에 전역적으로 공유하여 사용할 수 있다.
기존의 useState를 사용하여 컴포넌트 간에 state를 공유하려면, 상위 컴포넌트에서 하위 컴포넌트로 props를 전달하는 Prop Drilling
형태였다. 이는 컴포넌트의 깊이가 깊어질 경우, 중간에 있는 모든 컴포넌트를 거쳐서 props를 전달받기 때문에 props 추적이 어렵고 유지보수가 힘들다.
useContext를 사용하면 전역적으로 상태를 관리할 수 있기 때문에 중간에 있는 컴포넌트들을 건너뛰고 데이터가 필요한 컴포넌트에서 바로 사용이 가능하다.
⁕ useContext를 사용할 때는 createContext와 useContext 2개를 import 해야 한다.
createContext
로 context 객체를 생성할 수 있고, 생성된 context 객체는 Provider
를 통해 하위 컴포넌트에게 전달될 수 있다. useContext
로 context의 값을 불러와 전역적으로 사용 가능하다.
💡context = createContext(initialState)
💡<context.Provider value={provider value} />
💡contextValue = useContext(context)
단, useContext는 context value가 변경되면 useContext를 사용하고 있는 모든 컴포넌트가 재렌더링 된다. 따라서 useContext를 사용할 때 context value를 메모제이션 해줘야 한다.
📜 App.js
import React, { createContext } from "react";
import Child from "./Child";
// createContext를 통해 전역적으로 사용될 context 객체 생성
export const contextUser = createContext();
const App = () => {
const user = {
name: '홍길동',
age: 23
};
return (
<>
// Provider를 통해 생성한 context 객체를 하위 컴포넌트로 전달
// value는 user 객체를 넘김
<contextUser.Provider value={user}>
<div>
<Child />
</div>
</contextUser.Provider>
</>
);
};
export default App;
📜 Child.js
import React, { useContext } from "react";
import { contextUser } from "./App";
const Child = () => {
// useContext를 통해 바로 context value를 가져온다.
const user = useContext(contextUser);
return (
<>
<p>이름은 {user.name}입니다.</p>
<p>나이는 {user.age}살 입니다.</p>
</>
);
};
export default Child;
참고
📑 '데이터가 폭포수처럼 흘러내려' React의 flux 패턴
📑 React (Hooks) - useContext 란?