๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ’ฌ/ใ…ใ……ใ…Œใ…‹ใ…ใ…… ์ฑŒ๋ฆฐ์ง€

45์ผ์ฐจ

๐Ÿ˜ถ‍๐ŸŒซ๏ธ

45์ผ์ฐจ

 

Part 13. mobX๋กœ ์ƒํƒœ๊ด€๋ฆฌํ•˜๊ธฐ

 


 

 

Part 13. mobX๋กœ ์ƒํƒœ๊ด€๋ฆฌํ•˜๊ธฐ

 

 

https://slides.com/woongjae/mobx2021

 

MobX 2021

๋ชน์—‘์Šค 2021

slides.com

 

https://mobx.js.org/README.html

 

README · MobX

<img src="https://mobx.js.org/assets/mobx.png" alt="logo" height="120" align="right" />

mobx.js.org

 

 

 

  • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ ๊ทน ํ™œ์šฉํ•œ๋‹ค.
    • cra ์— ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•...
    • ์Šคํ† ์–ด ๊ฐ์ฒด์— ๋ถ™์ด๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ์žˆ๊ณ , => @observable
    • ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค. => @observer
  • TypeScript ๊ฐ€ Base ์ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค
  • Redux ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์Šคํ† ์–ด์— ํ•„์š”ํ•œ ๋ถ€๋ถ„๊ณผ ๋ฆฌ์•กํŠธ์— ํ•„์š”ํ•œ ๋ถ€๋ถ„์ด ์žˆ๋‹ค.
    • npm i mobx -D
    • npm i mobx-react -D
  • ๋ฆฌ๋•์Šค์™€ ๋‹ค๋ฅด๊ฒŒ ๋‹จ์ผ ์Šคํ† ์–ด๋ฅผ ๊ฐ•์ œํ•˜์ง„ ์•Š๋Š”๋‹ค.โ€‹

 

 

 

 ๐Ÿ”— ํ”„๋กœ์ ํŠธ์— decorator ์„ค์ •ํ•˜๊ธฐ 

 

$ npm i customize-cra react-app-rewired -D

$ npm i autobind-decorator

 

// ./jsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}
// ./package.json

{
  ...
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },
  ...
}
// ./config-overrides.js

const { override, addDecoratorsLegacy } = require("customize-cra");

module.exports = override(addDecoratorsLegacy());

 

// ./src/components/Button.jsx

import React from "react";
import autobind from "autobind-decorator";

export default class Button extends React.Component {
  render() {
    return <button onClick={this.click}>๋ฒ„ํŠผ</button>;
  }

  @autobind
  click() {
    console.log("clicked");
  }
}
// ./src/App.js

import logo from "./logo.svg";
import "./App.css";
import Button from "./components/Button";

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

export default App;

 

  • mobX with TypeScript
npm i customize-cra react-app-rewired -D # XXX

$ npm i autobind-decorator

 

// ./tsconfig.json

{
  "compilerOptions": {
    ...
    "experimentalDecorators": true
  },
  ...
}
// ./scr/components/Button.jsx

import autobind from "autobind-decorator";
import React from "react";

export default class Button extends React.Component {
  render() {
    return <button onClick={this.click}>ํด๋ฆญ</button>;
  }

  @autobind
  click() {
    console.log("clicked");
  }
}
// ./src/App.jsx

import React from "react";
import logo from "./logo.svg";
import "./App.css";
import Button from "./components/Button";

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

export default App;

 

 

 ๐Ÿ”— @observable (by mobx) 

 

# npm i mobx

 

  • observable(<value>)
    • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์—†์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹
    • @ ์—†์ด, ํ•จ์ˆ˜์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•ด์„œ ๋ฆฌํ„ดํ•œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉ
  • @observable <ํด๋ž˜์Šค์˜ ํ”„๋กœํผํ‹ฐ>
    • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•
    • ํด๋ž˜์Šค ๋‚ด๋ถ€์— ํ”„๋กœํผํ‹ฐ ์•ž์— ๋ถ™์—ฌ์„œ ์‚ฌ์šฉ
    • ํ•œ ํด๋ž˜์Šค ์•ˆ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ @observable ์กด์žฌ

 

// ./src/index.js

...
import { autorun, makeObservable, observable } from "mobx";

const isLogin = observable(true);

const person = observable({ name: "Mark", age: 39 });

class PersonStore {
  @observable
  name = "Mark";

  @observable
  age = 39;

  constructor() {
    makeObservable(this);
  }
}

const personStore = new PersonStore();

autorun(() => {
  console.log(isLogin.get());
  console.log(person.age);
  console.log(personStore.age);
});

isLogin.set(false);

person.age = 40;

personStore.age = 40;

...

 

 

 ๐Ÿ”— @observer (by mobx-react) 

 

# npm i mobx-react

 

 

  • observer(<์ปดํฌ๋„ŒํŠธ>);
    • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์—†์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹
    • ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ์— ์‚ฌ์šฉ
  • <์ปดํฌ๋„ŒํŠธ ํด๋ž˜์Šค> ์— @observer ๋‹ฌ์•„์„œ ์ฒ˜๋ฆฌ
    • โ€‹ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ์— ์‚ฌ์šฉ

 

// ./contexts/PersonContext.js

import { createContext } from "react";

const PersonContext = createContext();

export default PersonContext;
// ./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

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import PersonContext from "./contexts/PersonContext";
import PersonStore from "./stores/PersonStore";

const personStore = new PersonStore();

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

...

 

  • Functional Component
// ./App.js

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

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

  function click() {
    personStore.plus();
  }
}

export default observer(App);
  • Class Component
// ./App.js
// Class Component

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

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

  render() {
    const personStore = this.context;

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

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

export default App;

 

 

 ๐Ÿ”— @computed (by mobx) 

 

  • computed(๋‚ด๋ถ€์—์„œ observable ์„ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜);
    • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์—†์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹
  • <observable ํด๋ž˜์Šค> ์˜ getter ์— @computed ๋‹ฌ์•„์„œ ์ฒ˜๋ฆฌ
    • โ€‹์Šคํ† ์–ด์— ์‚ฌ์šฉ
    • getter ์—๋งŒ ๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค.
  • ํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ•˜๋‹ค๋Š” ๊ฒƒ์— ์ฃผ๋ชฉ
  • ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” (๊ฒŒํ„ฐ)๊ฐ’๋“ค์— ๋‹ฌ์•„์„œ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ์†Œ ๋ฒ”์œ„๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์œ ์šฉํ•˜๋‹ค.
    • 40์‚ด์ด ๋„˜์—ˆ์„๋•Œ๋งŒ ๋‚˜์ด๋ฅผ ์˜ฌ๋ฆฌ๋ฉด 40์‚ด ์ดํ•˜์ผ๋•Œ๋Š” ์žฌ๋žœ๋”๋ง ๋Œ€์ƒ์ด ์•„๋‹Œ ๊ฒƒ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ
    • ๋‚ด๋ถ€์ ์œผ๋กœ ๊ณ ๋„์˜ ์ตœ์ ํ™” => ์–ด๋–ป๊ฒŒ ?
      • ๋งค๋ฒˆ ์žฌ๊ณ„์‚ฐ์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค
      • ๊ณ„์‚ฐ์— ์‚ฌ์šฉํ•  observable ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉด ์žฌ์‹คํ–‰ํ•˜์ง€ ์•Š์Œ.
      • ๋‹ค๋ฅธ computed ๋˜๋Š” reaction ์— ์˜ํ•ด ํ˜ธ์ถœ๋˜์ง€ ์•Š์œผ๋ฉด ์žฌ์‹คํ–‰ํ•˜์ง€ ์•Š์Œ.
      • observable ์ด ๋ณ€ํ–ˆ๋Š”๋ฐ computed ๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์„๋•Œ ๋žœ๋”ํ•˜์ง€ ์•Š์Œ.

 

  • Funtional Component
// ./App.js

import logo from "./logo.svg";
import "./App.css";
import { observer } from "mobx-react";
import React, { useContext } from "react";
import PersonContext from "./contexts/PersonContext";
import autobind from "autobind-decorator";
import { computed } from "mobx";

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

  const age10 = computed(() => {
    return Math.floor(personStore.age / 10) * 10;
  }).get();
  
  console.log("render");
  
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>{age10}</p>
        <p>
          <button onClick={click}>plus</button>
        </p>
      </header>
    </div>
  );

  function click() {
    personStore.plus();
  }
}

export default observer(App);

 

  • Class Component
// ./App.js

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

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

  render() {
    console.log("render");
  
    const personStore = this.context;

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

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

export default App;
// ./stores/PersonStore.js

import { 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);
  }

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

 

 

 

 ๐Ÿ”— @action (by mobx) 

 

// ./App.js

import logo from "./logo.svg";
import "./App.css";
import { observer } from "mobx-react";
import React, { useContext } from "react";
import PersonContext from "./contexts/PersonContext";
import autobind from "autobind-decorator";
import { action, computed, runInAction } from "mobx";

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

  console.log("render", personStore.age, personStore.name);

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

  function click() {
    // personStore.plus();
    setTimeout(() => {
      personStore.testAction();
    }, 500);
  }
}

export default observer(App);
// ./stores/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);
  }

  plus() {
    this.age++;
  }

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

 

 

 ๐Ÿ”— @inject ์™€ Provider  => store๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•˜๋‹ค!

 

 

  • ๋„ค, ๊ทธ ํ”„๋กœ๋ฐ”์ด๋”๊ฐ€ ๋งž์Šต๋‹ˆ๋‹ค.
    • ๋„ค, ๊ทธ๋ž˜์„œ ์ปจํ…Œ์ด๋„ˆ๋ผ๋Š” ๊ฐœ๋…์„ ์‚ฌ์šฉํ•ด๋„ ์ข‹์Šต๋‹ˆ๋‹ค.
  • ํ”„๋กœ๋ฐ”์ด๋”์— props ๋กœ ๋„ฃ๊ณ , @inject ๋กœ ๊บผ๋‚ด ์“ด๋‹ค๊ณ  ์ƒ๊ฐํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
    • ์ƒ๋‹นํžˆ ๋ช…์‹œ์ ์ด๊ณ , ํŽธํ•ฉ๋‹ˆ๋‹ค.
    • ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์“ฐ์ง€ ์•Š์•„๋„ ๋ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
      • props ๋กœ ๋ฐ”๊ฟ”์ค๋‹ˆ๋‹ค.
      • this.props.store

 

// ./index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { observable } from "mobx";
import PersonContext from "./contexts/PersonContext";
import PersonStore from "./stores/PersonStore";
import { Provider } from "mobx-react";

const personStore = new PersonStore();

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

...

 

  • Functional Component
// ./App.js

import logo from "./logo.svg";
import "./App.css";
import { inject, observer } from "mobx-react";
import React, { useContext } from "react";
import PersonContext from "./contexts/PersonContext";
import autobind from "autobind-decorator";
import { action, computed, runInAction } from "mobx";

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

  console.log("render", personStore.age, personStore.name);

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

  function click() {
    setTimeout(() => {
      personStore.testAction();
    }, 500);
  }
}

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

 

  • Class Component
// ./App.js

import logo from "./logo.svg";
import "./App.css";
import { inject, observer } from "mobx-react";
import React, { useContext } from "react";
import PersonContext from "./contexts/PersonContext";
import autobind from "autobind-decorator";
import { computed } from "mobx";

@inject("personStore")
@observer
class App extends React.Component {
  // static contextType = PersonContext;

  render() {
    console.log("render");
    const { personStore } = this.props;

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

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

export default App;

 

  • AppContainer๋ฅผ ๋งŒ๋“ค๊ณ  AppComponent์—๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ
// ./App.js

import logo from "./logo.svg";
import "./App.css";
import { inject, observer } from "mobx-react";
import React, { useContext } from "react";
import PersonContext from "./contexts/PersonContext";
import autobind from "autobind-decorator";
import { computed } from "mobx";

@inject("personStore")
@observer
class AppContainer extends React.Component {
  render() {
    console.log("render");
    const { personStore } = this.props;

    return <App age10={personStore.age10} plus={this.plus} />;
  }

  @autobind
  click() {
    this.props.personStore.plus();
  }
}

function App({ age10, plus }) {
  console.log("render");

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

  function click() {
    plus();
  }
}

export default AppContainer;

 

 

 

 ๐Ÿ”— mobx-devtools 

 

https://github.com/mobxjs/mobx-devtools

 

GitHub - mobxjs/mobx-devtools: Mobx Devtools (React, Chrome Extension) - Looking for maintainers! https://github.com/mobxjs/mobx

Mobx Devtools (React, Chrome Extension) - Looking for maintainers! https://github.com/mobxjs/mobx-devtools/issues/55 - GitHub - mobxjs/mobx-devtools: Mobx Devtools (React, Chrome Extension) - Looki...

github.com

 

 

๐Ÿ”— stores (์Šคํ† ์–ด ์—ฌ๋Ÿฌ๊ฐœ ์‚ฌ์šฉํ•˜๊ธฐ)

 

// ./stores/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(rootStore) {
    makeObservable(this);
    this.rootStore = rootStore;
  }

  @action
  plus() {
    this.age++;
    this.rootStore.todoStore.todos = [];
  }

  @action
  testAction() {
    this.age = 45;
    this.name = "Woongjae";
  }
}
// ./stores/TodoStore.js

import { makeObservable, observable } from "mobx";

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

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

  constructor() {
    makeObservable(this);
  }
}
// ./stores/RootStore.js

import PersonStore from "./PersonStore";
import TodoStore from "./TodoStore";

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

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "mobx-react";
import RootStore from "./stores/RootStore";

const rootStore = new RootStore();

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

...

 

// ./containers/PersonContainer.jsx

import { inject } from "mobx-react";
import { observer } from "mobx-react-lite";
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));
// ./containers/TodoContainer.jsx

import { inject } from "mobx-react";
import { observer } from "mobx-react-lite";
import Todo from "../components/Todo";

const TodoContainer = ({ todoStore }) => {
  const todos = todoStore.todos;

  return <Todo todos={todos} />;
};

export default inject("todoStore")(observer(TodoContainer));
// ./containers/TodoFormContainer.jsx

import { inject } from "mobx-react";
import { observer } from "mobx-react-lite";
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));

 

// ./components/Person.jsx

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

  function click() {
    plus();
  }
}
// ./components/Todo.jsx

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>
  );
});
// ./components/TodoForm.jsx

import { useRef } from "react";

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

  return (
    <div>
      <p>
        <input ref={ref} />
        <button onClick={click}>add</button>
      </p>
    </div>
  );

  function click() {
    add(ref.current.value);
  }
}
// ./App.js

...

function App({ age10, plus }) {
  console.log("render");

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

export default App;

 

 

 

 ๐Ÿ”— Asynchronous actions 

 

$ npm i axios

 

// ./App.js

...

function App({ age10, plus }) {
  console.log("render");

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

export default App;
// ./stores/RootStore.js

import PersonStore from "./PersonStore";
import TodoStore from "./TodoStore";
import UserStore from "./UserStore";

export default class RootStore {
  constructor() {
    this.todoStore = new TodoStore(this);
    this.personStore = new PersonStore(this);
    this.userStore = new UserStore(this);
  }
}
// ./components/UserList.jsx

import { useEffect } from "react";

export default function UserList({ getUsers, users }) {
  useEffect(() => {
    getUsers();
  }, [getUsers]);

  return (
    <div>
      <u1>
        {users.map((user) => {
          <l1>{user.login}</l1>;
        })}
      </u1>
    </div>
  );
}

 

 

  • ์ปจํ…Œ์ด๋„ˆ์—์„œ ๋น„๋™๊ธฐ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๊ณ , ๋‹จ๊ณ„๋ณ„๋กœ state ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” action ์€ ๋ณ„๋„๋กœ ๋ถ„๋ฆฌํ•ด์„œ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹
// ./stores/UserStore.js

import { makeObservable, observable } from "mobx";

export default class UserStore {
  @observable
  state = {
    users: [],
    loading: false,
    error: null,
  };

  constructor() {
    makeObservable(this);
  }

  @action
  pending() {
    this.state.loading = true;
    this.state.error = null;
  }

  @action
  success(users) {
    this.state.users = users;
    this.state.loading = false;
    this.state.error = null;
  }

  @action
  fail(error) {
    this.state.loading = false;
    this.state.error = error;
  }
}
// ./containers/UserListContainer.jsx

import { inject } from "mobx-react";
import { observer } from "mobx-react-lite";
import { useCallback } from "react";
import UserList from "../components/UserList";
import axios from "axios";

const UserListContainer = ({ userStore }) => {
  const getUsers = useCallback(async () => {
    try {
      userStore.pending();
      const response = await axios.get("https://api.github.com/users");
      userStore.success(response.data);
    } catch (error) {
      userStore.fail(error);
    }
  }, [userStore]);

  const users = userStore.state.users;

  return <UserList getUsers={getUsers} users={users} />;
};

export default inject("userStore")(observer(UserListContainer));

 

  • action ์•ˆ์—์„œ ๋น„๋™๊ธฐ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๊ณ , state ๋ฅผ ๋ณ€๊ฒฝํ•ด์•ผ ํ•  ๋•Œ runInAction() ํ•จ์ˆ˜๋ฅผ ์จ์„œ ํƒ€์ด๋ฐ๋งˆ๋‹ค state ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ์‹ โญ
// ./stores/UserStore.js

import axios from "axios";
import { makeObservable, observable, runInAction } from "mobx";

export default class UserStore {
  @observable
  state = {
    users: [],
    loading: false,
    error: null,
  };

  constructor() {
    makeObservable(this);
  }

  @action
  pending() {
    this.state.loading = true;
    this.state.error = null;
  }

  @action
  success(users) {
    this.state.users = users;
    this.state.loading = false;
    this.state.error = null;
  }

  @action
  fail(error) {
    this.state.loading = false;
    this.state.error = error;
  }

  async getUser() {
    try {
      // pending
      runInAction(() => {
        this.state.loading = true;
        this.state.error = null;
      });
      const response = await axios.get("https://api.github.com/users");
      // success
      runInAction(() => {
        this.state.users = response.data;
        this.state.loading = false;
        this.state.error = null;
      });
    } catch (error) {
      runInAction(() => {
        this.state.loading = false;
        this.state.error = error;
      });
    }
  }
}
// ./containers/UserListContainer.jsx

import { inject } from "mobx-react";
import { observer } from "mobx-react-lite";
import { useCallback } from "react";
import UserList from "../components/UserList";

const UserListContainer = ({ userStore }) => {
  const getUsers = useCallback(() => {
    userStore.getUsers();
  }, [userStore]);
  const users = userStore.state.users;

  return <UserList getUsers={getUsers} users={users} />;
};

export default inject("userStore")(observer(UserListContainer));

 

  • generater ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋น„๋™๊ธฐ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹
// ./stores/UserStore.js

import axios from "axios";
import { makeObservable, observable, runInAction } from "mobx";

export default class UserStore {
  @observable
  state = {
    users: [],
    loading: false,
    error: null,
  };

  constructor() {
    makeObservable(this);
  }

  @action
  pending() {
    this.state.loading = true;
    this.state.error = null;
  }

  @action
  success(users) {
    this.state.users = users;
    this.state.loading = false;
    this.state.error = null;
  }

  @action
  fail(error) {
    this.state.loading = false;
    this.state.error = error;
  }

  @flow
  *getUsersFlow() {
    try {
      this.state.loading = true;
      this.state.error = null;
      const response = yield axios.get("https://api.github.com/users");
      // success
      this.state.users = response.data;
      this.state.loading = false;
      this.state.error = null;
    } catch (error) {
      this.state.loading = false;
      this.state.error = error;
    }
  }
}
// ./containers/UserListContainer.jsx

import { inject } from "mobx-react";
import { observer } from "mobx-react-lite";
import { useCallback } from "react";
import UserList from "../components/UserList";

const UserListContainer = ({ userStore }) => {
  const getUsers = useCallback(() => {
    userStore.getUsersFlow();
  }, [userStore]);
  const users = userStore.state.users;

  return <UserList getUsers={getUsers} users={users} />;
};

export default inject("userStore")(observer(UserListContainer));

 

'๐Ÿ’ฌ > ใ…ใ……ใ…Œใ…‹ใ…ใ…… ์ฑŒ๋ฆฐ์ง€' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

47์ผ์ฐจ  (0) 2022.03.29
46์ผ์ฐจ  (0) 2022.03.29
43, 44์ผ์ฐจ  (0) 2022.03.26
42์ผ์ฐจ  (0) 2022.03.26
41์ผ์ฐจ  (0) 2022.03.25