Front-End/MobX

@Observer

Voyage_dev 2022. 12. 15. 21:38

Observer에 대해서 알아보자.

Observer는 mobx-react에 있기 때문에 설치부터 해야된다.

npm i mobx-react

Observer는 크게 보면 두 가지 방식으로 쓰일 수 있다.

  • observer(<컴포넌트>)
    • 데코레이터 없이 사용하는 방식
    • 함수처럼 컴포넌트를 실행해서 그 결과물을 사용하는 방식
    • 보통 함수 컴포넌트에 사용
  • <컴포넌트 클래스>에 가장 위에 @observer 달아서 처리
    • 클래스 컴포넌트에 사용

기존에 사용했던 프로젝트에서 App 컴포넌트를 observer 처리를 해 보자

// App.js

import logo from "./logo.svg";
import "./App.css";
import Button from "./components/Button";
import { observer } from "mobx-react";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Button />
      </header>
    </div>
  );
}

export default observer(App); // <- observer

App 안에서 관찰하려고 하는 어떤 값들이 들어와 있어야 그 값이 변경됐을 때 다시 렌더를 한다. 지금은 App 컴포넌트 안에 observer 처리된 아이가 들어있지 않기 때문에 어떠한 변경사항도 없다.

 

index.js에 있는 observer로 처리된 personStore를 export 해서 App.js에서 가져간 다음에 그 안에서 표현을 해 보자.

// index.js

class PersonStore {
  @observable
  name = "Mark";

  @observable
  age = 39;

  constructor() {
    makeObservable(this);
  }

  plus() {
    this.age++;
  }
}

export const personStore = new PersonStore();
// App.js
import logo from "./logo.svg";
import "./App.css";
import Button from "./components/Button";
import { observer } from "mobx-react";
import { personStore } from "./index";

function App() {
  const click = () => {
    personStore.plus();
  };
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          <button onClick={click}>plus</button>
        </p>
        <p>{personStore.age}</p>
      </header>
    </div>
  );
}

export default observer(App);

이렇게 되면 간단하게 스토어 안에 있는 age의 값을 올리수가 있다. 굉장히 심플해지는게 personStore만 어디선가 전달받으면 이제 렌더를 다시 할 수 있는 로직을 만들 수 있다. personStore를 전달하는 방식은 가장 상위에서 하위로 props로 넘겨주는 방식이 있고 mobx-react에서 제공하는 provider로 context 처리 하면 어디서든 가져다가 사용할 수 있는 방법이 있다. 당연히 후자가 많이 쓰이는 방식이다.

이번에는 자체적으로 만든 context로 사용해 보자.

// contexts/PersonContext.js

import { createContext } from "react";

const PersonContext = createContext();

export default PersonContext;
// index.js

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

이제 PersonStore을 스토어 폴더로 옮기자

// stores/PersonStore.js

import { makeObservable, observable } from "mobx";

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

  @observable
  age = 39;

  constructor() {
    makeObservable(this);
  }

  plus() {
    this.age++;
  }
}

이제 index.js에서 스토어에 있는 PersonStore 클래스를 import 하고 Provider에 value로 넣어주자

// index.js 

const personStore = new PersonStore();

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

마지막으로 App.js에서 불러오자

import logo from "./logo.svg";
import "./App.css";
import { observer } from "mobx-react";
import { useContext } from "react";
import PersonContext from "./contexts/PersonContext";

function App() {
  const personStore = useContext(PersonContext);

  const click = () => {
    personStore.plus();
  };
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          <button onClick={click}>plus</button>
        </p>
        <p>{personStore.age}</p>
      </header>
    </div>
  );
}

export default observer(App);

동작이 잘 되는걸 확인 할 수 있다.

전역적으로 주입되는 store를 context로 넣어주고 그 store를 useContext를 이용해서 가져간 다음에 사용을 하게되면 store에 데이터를 변경시켰을 때 store에 값을 사용하고 있는 모든 컴포넌트들이 렌더가 다시 되는 방식이다.

 

이번에는 함수형을 클래스형으로 바꾸면서 observer를 달아보자

@observer
class App extends React.Component {
  static contextType = PersonContext;

  @autobind
  click() {
    const personStore = this.context;
    personStore.plus();
  }

  render() {
    const personStore = this.context;
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            <button onClick={this.click}>plus</button>
          </p>
          <p>{personStore.age}</p>
        </header>
      </div>
    );
  }
}

export default observer(App);

똑같이 작동한다!