Voyage_dev 2022. 12. 16. 15:39

 

이번에는 하나의 스토어가 아닌 여러개의 스토어를 사용해보자!

 

TodoStore와 RootStore를 만들어보자

// todoStore

import { observable } from "mobx";

export default class TodoStore {
  @observable
  todos = [];
}
// rootStore
import PersonStore from "./PersonStore";
import TodoStore from "./TodoStore";

export default class RootStore {
  constructor() {
    this.todoStore = new TodoStore();
    this.personStore = new PersonStore();
  }
}

이제 index.js에서는 TodoStore와 PersonStore를 각각 만들게 아니라 RootStore를 생성해서 Provider에 props로 넣어주자

// index.js

const rootStore = new RootStore();

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <Provider {...rootStore}>
      <App />
    </Provider>
  </React.StrictMode>
);

이렇게 넣게 되면 RootStore 안에 있는 .PersonStore와 .TodoStore가 각각 풀려서 들어가게 된다.

컨테이너를 만들어 코드들을 분리 해 보자

 

PersonContainer.jsx

import { inject, observer } from "mobx-react";
import { useCallback } from "react";
import Person from "../components/Person";

const PersonContainer = ({ personStore }) => {
  const age10 = personStore.age10;

  const plus = useCallback(() => {
    personStore.plus();
  }, [personStore]);

  return <Person age10={age10} plus={plus} />;
};

export default inject("personStore")(observer(PersonContainer));

Person 컴포넌트

export default function Person({ age10, plus }) {
  return (
    <div>
      <h1>{age10}</h1>
      <p>
        <button onClick={plus}>plus</button>
      </p>
    </div>
  );
}

기존에 있던 App.js 코드들은 사실상 대부분 필요가 없기 때문에 정리해 보자

import "./App.css";
import React from "react";
import PersonContainer from "./\\bcontainers/PersonContainer";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <PersonContainer />
      </header>
    </div>
  );
}

export default App;

이렇게 간단하게 분리하고 다시 실행해 보면 정상적으로 작동하지만

Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed.

액션을 달지 않은 observable들을 변경하는 메서드는 허락되지 않는다라는 경고가 뜬다. 그러면 personStore.plus() 하는게 지금 액션이 안 달려 있어서 그런거니 액션을 달아주자.

// personStore.js

import { action, computed, makeObservable, observable } from "mobx";

export default class PersonStore {
  @observable
  name = "Mark";

  @observable
  age = 39;

  @computed
  get age10() {
    return Math.floor(this.age / 10) * 10;
  }

  constructor() {
    makeObservable(this);
  }

  @action // <- 이 부분에 action을 달아주자
  plus() {
    this.age++;
  }

  @action
  testAction() {
    this.age = 45;
    this.name = "Kyusung";
  }
}

이제는 TodoContainer와 컴포넌트를 작성해 보자

// TodoContainer.jsx
import { inject, observer } from "mobx-react";
import Todo from "../components/Todo";

const TodoContainer = ({ todoStore }) => {
  const todos = todoStore.todos;
  return <Todo todos={todos} />;
};

export default inject("todoStore")(observer(TodoContainer));
// Todo.jsx
export default function Todo({ todos }) {
  if (todos.length === 0)
    return (
      <div>
        <h1>할 일이 없습니다</h1>
      </div>
    );
  return (
    <div>
      <ul>
        {todos.map(todo => (
          <li>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}
// app.js

import "./App.css";
import React from "react";
import PersonContainer from "./\\bcontainers/PersonContainer";
import TodoContainer from "./\\bcontainers/TodoContainer";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <PersonContainer />
        <TodoContainer />
      </header>
    </div>
  );
}

export default App;

결과

이제 todo 데이터를 추가해 보자

// TodoFormContainer.jsx

import { inject, observer } from "mobx-react";
import { useCallback } from "react";
import TodoForm from "../components/TodoForm";

const TodoFormContainer = ({ todoStore }) => {
  const add = useCallback(
    text => {
      todoStore.add(text);
    },
    [todoStore]
  );
  return <TodoForm add={add} />;
};

export default inject("todoStore")(observer(TodoFormContainer));
// TodoForm.jsx
import { useRef } from "react";

export default function TodoForm({ add }) {
  const ref = useRef();

  return (
    <div>
      <p>
        <input type="text" ref={ref} />
        <button onClick={() => add(ref.current.value)}>Add</button>
      </p>
    </div>
  );
}
// TodoStore
import { action, observable } from "mobx";

export default class TodoStore {
  @observable
  todos = [];

  @action
  add(text) {
    this.todos.push({ text, done: false });
  }
}
// App.js
import "./App.css";
import React from "react";
import PersonContainer from "./\\bcontainers/PersonContainer";
import TodoContainer from "./\\bcontainers/TodoContainer";
import TodoFormContainer from "./\\bcontainers/TodoFormContainer";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <PersonContainer />
        <TodoContainer />
        <TodoFormContainer />
      </header>
    </div>
  );
}

export default App;

이렇게 추가하고 동작해 보면

add 버튼을 눌러도 아무런 변화가 일어나지 않는다. 왜냐면 TodoStore에 우리가 todos를 observable로 설정해 줬지만 이 안에 있는 것은 constructor()를 통해서 makeObservable 함수를 호출하면서 this를 담아서 호출해 줘야 한다.

import { action, makeObservable, observable } from "mobx";

export default class TodoStore {
  @observable
  todos = [];

  @action
  add(text) {
    this.todos.push({ text, done: false });
  }

  constructor() {
    makeObservable(this);
  }
}

그리고 우리는 지금 TodoContainer에서 observer를 했기 때문에 Todo가 observer를 안 해도 된다고 생각할 수도 있지만 Todo가 mobx에 영향을 받아서 다시 렌더가 될려면 Todo에도 observer가 있어야 한다.

import { observer } from "mobx-react";

export default observer(function Todo({ todos }) {
  if (todos.length === 0)
    return (
      <div>
        <h1>할 일이 없습니다</h1>
      </div>
    );
  return (
    <div>
      <ul>
        {todos.map(todo => (
          <li>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
});

정상적으로 추가가 되는 걸 볼 수 있다.