상태(State)
는 어떠한 의미를 지닌 값이며 애플리케이션 시나리오에 따라 지속적으로 변경 될 수 있는 값을 의미합니다. 현대 애플리케이션은 상태의 종류를 여러가지 분류로 구분지을 수 있기 때문에 각각의 카데고리가 어떠한 상태를 갖는 지에 대해서 이야기 해보겠습니다.http://localhost:3000/rooms/31232?adults=2
와 같은 주소는 roomId는 31232 adult=2 라는 상태를 갖고 있고 이러한 상태는 주로 라우팅 시 사용되고 관리됩니다.pending
, idle
, loading
과 같이 사용자가 폼을 이용해서 전송하는 데이터의 진행 상황등의 상태 데이터가 존재합니다.데이터(Model)
와 화면 업데이트(View)
, 사용자 입력 처리(Controller)
의 책임을 분리하여 의존성을 줄이기 위한 목적으로 등장한 디자인 패턴입니다.register
와 dispatch
메서드입니다.register()
메서드를 통해 Store의 콜백을 등록하고, dispatch()
메서드를 사용해 register를 이용하여 등록되어 있는 콜백함수들을 Store에 전달하여 실행 시키는 역할을 수행합니다. 간단한 사용 예시는 아래와 같습니다.리덕스 (Redux)
일 것입니다. 리덕스가 유명한 이유는 Flux 패턴을 적용하여 상태관리의 어려움을 해결하고자 했던 최초의 라이브러리였기 때문입니다. 리덕스는 기존의 Flux 패턴의 아이디어를 확장하고 추가로 Elm 아키텍쳐의 몇 가지 주요 개념을 차용하여 설계되었습니다.UserStore
라는 Store를 만들었습니다. 그리고 이 후에 사용자의 팔로워와 팔로잉 리스트를 관리하기 위하여 추가로 FollwerStore
스토어를 생성했습니다.UserStore
가 먼저 업데이트되고, FollowerStore
에서 UserStore
의 데이터를 참조해 이름을 업데이트해야 합니다. 만약 이 과정에서 순서를 잘못 처리할 경우 스토어에 저장되어 있는 데이터가 최신 데이터를 반영하고 있지 않는 문제가 발생할 수 있습니다.단일 상태
를 통해서 상태를 관리하려고 했던 것입니다. Flux 패턴은 다중 상태
를 갖는다는 문제가 있었습니다. 다중 상태는 특정 상태가 어디에서 관리되고 변경되는지 파악하기 어려울 수 있습니다. 또한 Store간의 의존성을 가질 수 있다는 문제가 있습니다. 예를들어 보겠습니다.Update
)를 통해 이루어진다는 특징을 갖고 있습니다. Redux는 이러한 다중 상태로 인해서 예측이 어렵고 서로 의존성을 갖게 되는 문제를 해결하기 위하여 Elm 아키텍처를 도입한 것입니다.Count1: 0
위의 버튼을 클릭하면서 랜더링이 어떻게 일어나는지 확인 해주세요!
의존성을 주입
하는 것과 유사하다는 표현이 생긴 것입니다.위의 버튼을 클릭하면서 랜더링이 어떻게 일어나는지 확인 해주세요!
get
메서드는 함수로 작성이 되어 있고 set
함수의 경우 특정 값으로 상태를 바로 업데이트 시키거나 함수를 이용하여 이전 상태를 기반으로 현재 상태 값을 변화 시키는 것이 가능합니다.subscribe
함수입니다. 어째서 subscribe 함수가 중요한 역할을 수행하는 지 알아보겠습니다.{
type: "ADD_TODO",
payload: { text: "Learn Flux" }
}
module Main exposing (..)
-- MODEL
type alias Model = Int
init: Model
init =
0
-- UPDATE
type Msg
= Increment
update: Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
-- VIEW
view: Model -> Html Msg
view model =
div []
[ button [ onClick Increment ] [text "+"]]
<div>
<button>+</button>
</div
Event 발생 -> view -> update -> model
View가 사용자가 이벤트를 트리거 -> Update가 특정 메시지를 기반으로 Model을 업데이트
type SomeContextType = {
count: number;
setCount: React.Dispatch<React.SetStateAction<number>>;
text: string;
setText: React.Dispatch<React.SetStateAction<string>>;
};
const SomeContext = React.createContext<SomeContextType | undefined>(undefined);
Context.Provider가 value를 갖고 있고, 이를 Fiber 트리상에서 이 Context를 쓰는 Consumer들이 어디에 있는지를 추적합니다.
-> Provider의 value가 바뀌면, React는 “새로운 value가 이전 value와 동일한지?”를 판단하고, 다르다면(주로 `Object.is`로 비교) 해당 Context를 구독하는 Consumer 부분(Fiber Subtree)에 이 Context가 업데이트됨을 알려줍니다.
-> 그 Consumer들은 다음 렌더 단계에서 “useContext(Context)”로 읽은 “value”가 달라진 것을 감지, 리렌더링이 필요해집니다.
class Dispatcher {
constructor() {
this._subscribedCallbacks = new Set();
}
register(callback) {
this._subscribedCallbacks.add(callback);
}
dispatch(action) {
for (const callback of this._subscribedCallbacks) {
callback(action); // Action을 각 콜백으로 전달
}
}
}
const todoDispatcher = new Dispatcher();
todoDispatcher.register((action) => {
switch (action.type) {
case "ADD_TODO":
Store.handleAction(action);
break;
}
});
const TodoAction = {
addTodo: (text) =>
todoDispatcher.dispatch({
type: "ADD_TODO",
payload: { text },
}),
};
const TodoStore = {
todos: [],
listeners: new Set(),
getTodos: function () {
return this.todos;
},
addTodo: function (text) {
this.todos.push({ id: Date.now(), text });
this.emitChange();
},
emitChange: () => {
for (const listener of listners) {
listener();
}
},
};
// UserStore
const UserStore = {
users: { 1: { name: "Alice", profilePicture: "alice.jpg" } },
};
// PostStore
const PostStore = {
posts: [{ id: 1, content: "Hello, World!", authorId: 1 }],
};
// UI에서 의존성 문제 발생
function renderPost(postId) {
const post = PostStore.posts.find((p) => p.id === postId);
const author = UserStore.users[post.authorId]; // 의존성 증가
console.log(`${author.name}: ${post.content}`);
}
function ContextApiProblem() {
return (
<SomeProvider>
{" "}
<div className="flex items-center justify-center gap-4">
{" "}
<div className="flex items-center justify-center flex-col">
<SomChildDisplay />
<SomeChildComponent /> {" "}
</div>
{" "}
<div className="flex items-center justify-center flex-col">
<TextEditor /> {" "}
</div>
{" "}
</div>
{" "}
</SomeProvider>
);
}
function Counter1() {
const state = get();
function handleClick() {
set((prev) => ({ count: prev.count + 1 }));
}
return (
<div>
<h3>Counter 1 : {state.count}</h3> {" "}
<button onClick={handleClick}>+</button> {" "}
</div>
);
}
type State<T> = {
get: () => T;
set: (state: T | (prev: T) => T) => T;
subscribe: (callback: () => void) => () => void
}
// store.ts
let state = { counte: 0 };
export const get = () => state;
export const set = (newState: State | ((prevState: State) => State)) => {
state =
typeof newState === "function"
? (newState as (prevState: State) => State)(state)
: newState;
};