상태(state) 관리란?
애플리케이션이 가지고 있는 데이터의 변경을 추적하고, 이에 대응하는 UI를 적절히 업데이트 하는 과정이다.
애플리케이션의 데이터를 효율적으로 관리하는 것이다.
(서버의 응답, 사용자의 입력, UI 등 애플리케이션 데이터에 대해 관리하는 것)
suhyeon : 백엔드에서 데이터가 변경되었을 때 ui에도 반영이 되어야하고,
사용자에게서 값을 입력 받아와 데이터를 변경해야한다. 이런 부분을 관리하는 것을 상태 관리라고 한다.
상태 == 데이터라고 생각하면 이해하기 쉽다.
인스타 팔로워, 게시물 개수 등을 생각하면 된다.
인스타 팔로워 수를 받아와 화면에 정확한 수가 보여야 하고, 게시물을 올렸을 때 올린 게시물의 수가 변해야 한다.
전역상태와 지역상태로 나뉜다.
지역상태는 부모 컴포넌트에서 깊이가 깊지 않은 자식 컴포넌트에게 데이터를 전달해서 그 데이터를 사용하는 것이다.
이때 상태를 전달하는 방법은 props를 사용한다.
이 지역상태관리는 데이터를 한 곳에서만 사용하는 것이 효율적이다. 이 부분은 여러 부분에서 데이터를 사용하기 불편하다는 단점이 존재한다.
따라서, 전역상태관리를 통해 여러 곳에서 사용할 수 있도록 데이터를 관리한다.
전역상태관리는 여러 컴포넌트에서 사용해야하는 데이터를 한 곳에서 관리하는 방법이다.
'react' 패기지에서 createContext를 불러와 사용
import { createContext } from ‘react’;
export const themeContext = createContext(전달할 데이터의 초기값);
export default function App() {
return (
<themeContext.Provider value={전달할 데이터}>
<Theme />
</themeContext.Provider>
)
}
function Theme() {
return (
<themeContext.Consumer>
{value => <div>{value}</div>}
</themeContext.Consumer>
)
}
(리액트 hook이 도입된 16.8 버전부터 사용 가능하게 된 방법)
import { createContext, useContext } from ‘react’;
export const themeContext = createContext(전달할 데이터의 초기값);
export default function App() {
return (
<themeContext.Provider value={전달 데이터}>
<Theme />
</themeContext.Provider>
)
}
function Theme() {
const theme = useContext(themeContext);
return <div>{theme}</div> // Provider에서 value로 전달한 데이터 출력
}
import { createContext, useCallback, useContext } from 'react';
const MyContext = createContext();
function App() {
return (
<MyContext.Provider value={"hello world"}>
<GrandParent></GrandParent>
</MyContext.Provider>
);
}
function GrandParent() {
return<Parent/>;
}
function Parent() {
return<Child/>;
}
function Child() {
return <GrandChild/>;
}
function GrandChild() {
return <Message/>;
}
function Message() {
const value = useContext(MyContext);
return <div>Recevied(useContext): {value}</div>
}
// function Message() {
// return(
// <MyContext.Consumer>
// {value => <div>Recevied(consumer): {value}</div>}
// </MyContext.Consumer>
// );
// }
export default App;
Provider을 사용하여 데이터를 제공하고, useContext를 이용해 데이터를 사용한다.
장점
접근성 -> React에서 제공하는 기술이라 추가 설치가 필요없다.
Redux나 MobX에 비해 러닝커브가 낮다 (쉽게 사용 가능)
사용하기 위한 코드 구성이 매우 간결하다.
단점
Context의 Provider는 value값이 변할 경우 해당 값을 사용하는 컴포넌트 또한 리렌더링된다. 컨텍스트를 상태값과 액션값으로 나누어 사용하면 해결이 가능하지만 보일러 플레이트 코드(Boilerplate code)가 증가하며 복잡해질수록 Provider가 늘어나 지옥을 맛볼 수 있다.
컴포넌트 간의 상태를 트리 구조로 관리하며,
스토어(Store), 액션(Action), 리듀서(Reducer)를 통해 상태를 업데이트한다.
상태가 관리되는 하나의 공간
앱에서 스토어에 운반할 데이터
Action을 Store에 바로 전달하는 것이 아닌 Reducer에 먼저 전달
Action을 전달받은 Reducer가 Store의 상태를 업데이트
--> 전달하기 위해 dispatch 이용
store의 내장함수로 action을 파라미터로 전달하고 reducer 호출
const redux = require('redux');
const counterReducer = (state = { counter :0 }, action) => {
if(action.type === "increment"){
return {
counter: state.counter + 1,
};
}
if(action.type === "decrement"){
return{
counter: state.counter -1,
};
}
return state;
};
const store = redux.createStore(counterReducer);
console.log(store.getState);
const counterSubscriber = () => {
const latestState = store.getState();
console.log(latestState);
};
store.subscribe(counterSubscriber);
store.dispatch({type: "increment"});
store.dispatch({type: "decrement"});
장점
상태의 변화를 예측 가능한 방식으로 처리하기 때문에 디버깅이 용이하다.
상태가 중앙에서 관리되므로, 컴포넌트 간의 상태 전달이 쉽고 예측이 가능하다.
미들웨어(Middleware)를 사용하여 비동기 작업을 처리할 수 있다.
단점
Redux는 많은 코드를 필요로 하며, 처음 학습하는데 러닝커브가 오래걸린다.
규모가 큰 프로젝트에서 사용하면 좋지만 작은 규모라면 굳이 Redux를 사용할 필요가 없다.
모든 상태 변화가 일어나는 부분을 자동으로 추적해주는 역할을 한다.
observable / action 으로 구성되어 있다.
어떤 Action이 실행되면 관찰하는 값(observable)이 변경된다.
관리하고자 하는 상태를 observable로 정의해 주어야 한다.
예를 들어
constructor() {
makeObservable(this, {
num: observable,
});
}
이런식으로 설정하면 된다.
상태를 변경하는 모든 작업을 Action으로 정의해 주어야 한다.
예를 들어
constructor() {
makeObservable(this, {
num: observable,
add: action,
});
}
add() {
this.num += 1;
}
이런식으로 설정하면 된다.
makeObservable 내에 computed를 정의할 수 있다.
computed는 직접 업데이트 되는 프로퍼티가 아니라, 상태의 변화에 의해 파생된 값이다.
import { action, makeObservable, observable } from "mobx";
class CounterStore{
number = 0
constructor() {
makeObservable(this, {
number : observable,
increase : action,
decrease : action,
})
}
increase = () => {
this.number++;
};
decrease = () => {
this.number--;
};
}
export default CounterStore;
store에서 observable과 action을 정의해주면 된다.
import React from "react";
import { observer } from "mobx-react";
const Counter = observer(({ counter }) => (
<div>
<h1>{counter.number}</h1>
<button onClick={counter.increase}>+1</button>
<button onClick={counter.decrease}>-1</button>
</div>
));
export default Counter;
장점
객체지향적 -> MobX는 객체지향적으로 class를 사용을 권장한다.
캡슐화 -> state의 변경을 오직 메서드를 통해서만 변경할 수 있게 설정이 가능하다.
코드가 간결하며, Redux와 달리 작은 규모의 애플리케이션에서 사용하기 쉽다.
컴포넌트에서 상태를 직접 관찰할 수 있어, 불필요한 리렌더링을 방지할 수 있다.
단점
Redux에 비해 다른 개발자들과의 협업이 어려울 수 있다.
디버깅이 불편하다 -> Redux와 같은 툴이 없어 디버깅을 위해선 console.log를 이용해야 한다.
레퍼런스 코드가 부족하다.