todo 아이템과 같은 도메인 별 state
현재 선택된 요소와 같은 view state
원하는 데이터 구조에 state을 저장한다
import { makeObservable, observable, action } from "mobx"
class Todo {
id = Math.random()
title = ""
finished = false
constructor(title) {
makeObservable(this, {
title: observable,
finished: observable,
toggle: action
})
this.title = title
}
toggle() {
this.finished = !this.finished
}
}
사용자 이벤트, 백엔드 데이터 푸시, 예약된 이벤트 등과 같이 state를 변경하는 코드 조각
observable을 변경하는 코드는 action으로 표시
현재의 state를 기반으로 새로운 정보를 계산하는 view와는 다르다
사용자 인터페이스, 남은 todos의 수와 같은 파생 데이터, 백엔드 통합
- computed 값 : 현재의 observable state에서 순수 함수를 사용하여 파생될 수 있는 값
import { makeObservable, observable, computed } from "mobx"
class TodoList {
todos = []
get unfinishedTodoCount() {
return this.todos.filter(todo => !todo.finished).length
}
constructor(todos) {
makeObservable(this, {
todos: observable,
unfinishedTodoCount: computed
})
this.todos = todos
}
}
todo가 추가되거나 finished 속성 중 하나가 수정될 때 unfinishedTodoCount를 자동으로 업데이트한다
- reaction : state가 변경될 때 자동으로 발생해야 하는 부수효과 (명령형 프로그램밍과 반응형 프로그래밍 사이를 연결해주는 다리 효과)
GUI의 일부를 다시그리는 행동
콘솔 출력, 네트워크 요청, DOM 패치 적용을 위해 React 컴포넌트 트리를 점진적으로 업데이트하는 부수효과를 생성
action과 reaction 모두 부수효과를 일으킬 수 있다
form 을 제출할 때 네트워크 요청을 하는 것 처럼, 트리거 될 수 있는 명확하고 명시적인 출처가 있는 부수효과는 관련 이벤트 핸들러에서 명시적으로 트리거 되어야 한다
어떻게? >> observer 함수를 이용하여 컴포넌트를 감싼다
import * as React from "react"
import { render } from "react-dom"
import { observer } from "mobx-react-lite"
const TodoListView = observer(({ todoList }) => (
<div>
<ul>
{todoList.todos.map(todo => (
<TodoView todo={todo} key={todo.id} />
))}
</ul>
Tasks left: {todoList.unfinishedTodoCount}
</div>
))
const TodoView = observer(({ todo }) => (
<li>
<input type="checkbox" checked={todo.finished} onClick={() => todo.toggle()} />
{todo.title}
</li>
))
const store = new TodoList([new Todo("Get Coffee"), new Todo("Write simpler code")])
render(<TodoListView todoList={store} />, document.getElementById("root"))
onClick 핸들러는 toggle action을 사용할 때 적절한 TodoView 컴포넌트를 강제로 다시 렌더링하지만,
TodoListView 컴포넌트는 완료되지 않은 작업의 수(unfinishedTodoCount)가 변경된 경우에만 다시 렌더링 된다.
1. state가 변경되면 derivation이 자동, 원자 단위로 업데이트된다
2. derivation은 동기식으로 업데이트한다
3. computed는 느리게 업데이트된다
4. computed값은 순수해야 하며, state를 바꾸면 안된다
스토어의 주요 책임은 컴포넌트의 로직과 state를 프론트엔드 및 백엔드에서 사용할 수 있고 독립으로 테스트할 수 있는 단위로 만드는 것
도메인 state 저장소와 UI state 저장소를 분리함으로써 도메인 state를 재사용하고 테스트할 수 있는 장점이 있고
다른 애플리케이션에서 재사용할 수 있다
하나의 도메인 스토어는 애플리케이션에서 하나의 개념을 담당해야 한다
여러 도메인 객체가 내부에 있는 트리 구조로 구성될 수 있다
스토어는 도메인 객체를 인스턴스화하며 도메인 객체가 자신이 속한 저장소를 알고 있는지 확인한다
각 도메인 객체의 인스턴스가 하나만 있는지 확인한다
백엔드에서 업데이트 내용을 받은 경우 기존 인스턴스를 업데이트한다
스토어를 테스트할 수 있고, 서버 측에서 실행할 수 있는지 확인하기 위해 HTTP 요청을 별도의 객체로 이동하여 통신 계층을 추상화할 수 있어야 한다
스토어 인스턴스는 하나만 있어야 한다
자체 클래스(생성자 함수)를 사용하여 표현해야 한다.
메서드를 가질 수 있다
import { makeAutoObservable, autorun, runInAction } from "mobx"
import uuid from "node-uuid"
export class TodoStore {
authorStore
transportLayer
todos = []
isLoading = true
constructor(transportLayer, authorStore) {
makeAutoObservable(this)
this.authorStore = authorStore // 작성자를 확인할 수 있는 스토어
this.transportLayer = transportLayer // 서버 요청을 할 수 있는 것
this.transportLayer.onReceiveTodoUpdate(updatedTodo =>
this.updateTodoFromServer(updatedTodo)
)
this.loadTodos()
}
// 서버에서 모든 todo를 가져옵니다.
loadTodos() {
this.isLoading = true
this.transportLayer.fetchTodos().then(fetchedTodos => {
runInAction(() => {
fetchedTodos.forEach(json => this.updateTodoFromServer(json))
this.isLoading = false
})
})
}
// 서버의 정보로 Todo를 업데이트합니다. Todo가 한 번만 존재함을 보장합니다.
// 새로운 Todo를 생성하거나 기존 Todo를 업데이트하거나
// 서버에서 삭제된 Todo를 제거할 수 있습니다.
updateTodoFromServer(json) {
let todo = this.todos.find(todo => todo.id === json.id)
if (!todo) {
todo = new Todo(this, json.id)
this.todos.push(todo)
}
if (json.isDeleted) {
this.removeTodo(todo)
} else {
todo.updateFromJson(json)
}
}
// 클라이언트와 서버에 새로운 Todo를 생성합니다.
createTodo() {
const todo = new Todo(this)
this.todos.push(todo)
return todo
}
// Todo가 어떻게든 삭제되었을 때 클라이언트 메모리에서 삭제합니다.
removeTodo(todo) {
this.todos.splice(this.todos.indexOf(todo), 1)
todo.dispose()
}
}
// 도메인 객체 Todo.
export class Todo {
id = null // Todo의 고유 id, 변경할 수 없습니다.
completed = false
task = ""
author = null // authorStore에서 가져온 Author 객체에 대한 참조
store = null
autoSave = true // Todo의 변경사항을 서버에 제출하기 위한 표시
saveHandler = null // todo를 자동저장하는 부수효과의 Disposer(dispose).
constructor(store, id = uuid.v4()) {
makeAutoObservable(this, {
id: false,
store: false,
autoSave: false,
saveHandler: false,
dispose: false
})
this.store = store
this.id = id
this.saveHandler = reaction(
() => this.asJson, // JSON에서 사용되는 모든 것을 관찰합니다.
json => {
// autoSave가 true이면 JSON을 서버로 보냅니다.
if (this.autoSave) {
this.store.transportLayer.saveTodo(json)
}
}
)
}
// 클라이언트와 서버에서 해당 Todo를 제거합니다.
delete() {
this.store.transportLayer.deleteTodo(this.id)
this.store.removeTodo(this)
}
get asJson() {
return {
id: this.id,
completed: this.completed,
task: this.task,
authorId: this.author ? this.author.id : null
}
}
// 서버의 정보로 Todo를 업데이트합니다.
updateFromJson(json) {
this.autoSave = false // 변경 사항을 서버로 다시 보내는 것을 방지합니다.
this.completed = json.completed
this.task = json.task
this.author = this.store.authorStore.resolveAuthor(json.authorId)
this.autoSave = true
}
// observer를 청소합니다.
dispose() {
this.saveHandler()
}
}
import { makeAutoObservable, observable, computed, asStructure } from "mobx"
export class UiState {
language = "en_US"
pendingRequestCount = 0
// .struct는 dimension 객체가 deepEqual 방식으로
// 변경되지 않는 한 observer가 신호를 받지 않도록 합니다.
windowDimensions = {
width: window.innerWidth,
height: window.innerHeight
}
constructor() {
makeAutoObservable(this, { windowDimensions: observable.struct })
window.onresize = () => {
this.windowDimensions = getWindowDimensions()
}
}
get appIsInSync() {
return this.pendingRequestCount === 0
}
}
모든 저장소를 인스턴스화하고 참조를 공유하는 RootStore 만들기
Webpack ? (0) | 2024.07.03 |
---|---|
[우아한 타입스크립트 with 리액트] 타입의 세계 : 타입스크립트만의 독자적 타입 시스템 (0) | 2024.06.30 |
[Mobx & React] Flux 아키텍쳐에서부터 Mobx 간단한 적용 코드까지 (0) | 2024.02.27 |
[TypeScript] TypeScript에서 Interface와 type-alias 차이 (0) | 2024.02.21 |
[Javascript] Array 관련 함수 (0) | 2021.09.24 |
댓글 영역