๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป stateful logic sharing

๊น€์ฒ ์ค€ยท2022๋…„ 1์›” 20์ผ
0

REACT

๋ชฉ๋ก ๋ณด๊ธฐ
12/33

์–ด๋– ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์œ ๋™์ ์œผ๋กœ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•˜์—ฌ state๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
state๋Š” ํ•œ ๋ถ€๋ถ„์—์„œ๋งŒ ์“ฐ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๋ถ€๋ถ„์œผ๋กœ ์ „๋‹ฌํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.
์ด๋Ÿฌํ•œ state๋ฅผ ์–ด๋–ป๊ฒŒ ํšจ์œจ์ ์œผ๋กœ ์—ฌ๋Ÿฌ ๋ถ€๋ถ„์—์„œ ์“ธ ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด์ž.

โœ”๏ธ stateful logic

stateful logic์ด๋ž€ state๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งํ•œ๋‹ค.

โœ”๏ธ stateful logic sharing

stateful logic sharing์ด๋ž€ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์–ด๋– ํ•œ state๋ฅผ ๊ณต์œ ํ•˜์—ฌ ์“ฐ๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

stateful logic sharing์„ ์œ„ํ•˜์—ฌ props๋กœ state๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

๐Ÿšฉ props๋กœ state ์ „๋‹ฌ

App.js

import React, { Component } from "react";
import Passprops from "./Passprops";
import Passprops2 from "./Passprops2";

class App extends Component {
  state = {
    number: 0,
  };
  render() {
    return (
      <div>
        <Passprops number={this.state.number} />
        <Passprops2 number={this.state.number} />
      </div>
    );
  }
}

export default App;

Passprops.js

import React, { Component } from "react";

class Passprops extends Component {
  render() {
    return <div>Passprops1:{this.props.number}</div>;
  }
}

export default Passprops;

Passprops2.js

import React, { Component } from "react";

class Passprops2 extends Component {
  render() {
    return <div>Passprops2:{this.props.number}</div>;
  }
}

export default Passprops2;

๋‹ค์Œ๊ณผ ๊ฐ™์ด App.js์— ์žˆ๋Š” state๊ฐ’์„ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค.
(stateful logic sharing)

ํ•˜์ง€๋งŒ ๋งŒ์•ฝ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค๋ฅด๋‹ค๋ฉด state๋ฅผ ์“ธ ์ˆ˜ ์—†๋‹ค.
์ฆ‰, ์œ„์˜ ์˜ˆ๋ฅผ ์ฐธ๊ณ ํ•˜์ž๋ฉด App.js์—์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์–ธ๊ธ‰ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ทธ ์ปดํฌ๋„ŒํŠธ๋Š” App.js์— ์žˆ๋Š” state๋ฅผ ์“ธ ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด๋‹ค.

์ด๋ฅผ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ HOCs์™€ render props๊ฐ€ ์ œ์•ˆ๋œ๋‹ค.


๐Ÿฉบ stateful logic sharing solution

1. HOCs(Higher Order Components)

HOCs๋Š” ์–ด๋– ํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”์šฑ ํ–ฅ์ƒ๋œ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ณ€ํ˜•ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜์ด๋‹ค.

const EnhancedComponent = higherOrderComponent(WrappedComponent);
  • HOCs๋Š” stateful logice sharing์˜ ํ•ด๊ฒฐ๋ฒ•์œผ๋กœ ๋งŽ์ด ์“ฐ์ธ๋‹ค.
  • HOCsํ•จ์ˆ˜์— ์ธ์ˆ˜๋กœ state๋ฅผ ์“ฐ๊ณ ์žํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ• ๋‹นํ•œ๋‹ค.
  • HOCs๋Š” API๊ฐ€ ์•„๋‹Œ ๊ตฌ์„ฑ์„ ํ•˜๊ธฐ ์œ„ํ•œ ํŒจํ„ด์ด๋‹ค.

๋‹ค์Œ์€ stateful logice sharing์„ ์œ„ํ•œ HOCs์˜ ํŒจํ„ด ์˜ˆ์‹œ์ด๋‹ค.

App.js

import React from "react";

function withCounter(WrappedComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props);

      this.state = {
        count: 0,
      };
    }

    increment = () => {
      this.setState((prevState) => ({ count: prevState.count + 1 }));
    };

    render() {
      return <WrappedComponent count={this.state.count} increment={this.increment} {...this.props} />;
    }
  };
}

class Component1 extends Component {
  render() {
    const { count, increment } = this.props;
    return (
      <div>
        <button onClick={increment}>Clicked {count} times!</button>
      </div>
    );
  }
}


export const DerivedComponent = withCounter(Component1);

index.js

import React from "react";
import ReactDOM from "react-dom";
import { DerivedComponent } from "./App";

ReactDOM.render(
  <React.StrictMode>
    <DerivedComponent />
  </React.StrictMode>,
  document.getElementById("root")
);

โœ๏ธ ์‚ฌ์šฉ๋ฒ•

์œ„์™€ ๊ฐ™์ด withCounterํ•จ์ˆ˜(HOCsํ•จ์ˆ˜)๋Š” ์ปดํฌ๋„ŒํŠธ(Component1)๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค.
์ปดํฌ๋„ŒํŠธ(Component1)๋Š” state๋ฅผ ์ „๋‹ฌ๋ฐ›์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

HOCsํ•จ์ˆ˜(withCounter)์˜ ๋ฐ˜ํ™˜๊ฐ’์„ state๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š” ์ปดํฌ๋„ŒํŠธ(DerivedComponent)์— ํ• ๋‹นํ•œ๋‹ค.

์ด์™€ ๊ฐ™์ด HOCs๋Š” state๋ฅผ ์ „๋‹ฌํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋กœ์ง์„ ๊ตฌ์„ฑํ•ด์ฃผ์–ด ํ•จ์ˆ˜์— ์ธ์ˆ˜๋กœ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋Œ€์ž…ํ•ด์ฃผ๋ฉด state๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” stateful logic sharing์˜ ๋ฐฉ๋ฒ•์ค‘ ํ•˜๋‚˜์ด๋‹ค.

๋”๋ถˆ์–ด HOCs ํŒจํ„ด์€ stateful logic sharing๋ฟ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ํšจ์œจ์ ์ธlogic์„ ๊ตฌ์„ฑํ•˜๊ธฐ์œ„ํ•ด์„œ๋„ ์“ฐ์ธ๋‹ค.

โ—๏ธ ์ฃผ์˜์‚ฌํ•ญ

HOCsํ•จ์ˆ˜๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ์ฃผ์˜ํ•ด์•ผํ•  ์‚ฌํ•ญ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • HOC ๋‚ด๋ถ€์—์„œ ์ปดํฌ๋„ŒํŠธ์˜ ๋ณ€ํ˜•์‹œํ‚ค์ง€ ์•Š์•„์•ผํ•œ๋‹ค.
    ์ด๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”ฐ๋กœ ์‚ฌ์šฉํ•  ๋•Œ์—๋„ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜ํ–ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

  • render()์— HOCs๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋œ๋‹ค.
    render()๋Š” ์ด์ „ ํŠธ๋ฆฌ์™€ ๋น„๊ตํ•˜์—ฌ ์ด์ „๊ฒƒ์„ unmountํ•˜๊ณ  ๋ณ€ํ™”๋œ ๊ฒƒ์„ ๋ฐ˜์˜ํ•˜๋Š”๋ฐ ์—ฌ๊ธฐ์— HOCs๋กœ ๊ฐ์‹ผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ „์˜ state๊ฐ’์ด unmount๋˜๊ณ  ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค๋„ unmount๋  ์ˆ˜ ์žˆ๋‹ค.

2. render props

stateful logic sharing์˜ ๋‘๋ฒˆ์งธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” render props๊ฐ€ ์žˆ๋‹ค.

render prop์€ prop์œผ๋กœ ๋ Œ๋”ํ•  ๋‚ด์šฉ์„ ํ• ๋‹นํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
์šฐ์„  Counter๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

Counter.js

import React, { Component } from "react";

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };
  render() {
    return <div>{this.props.render(this.state.count, this.increment)}</div>;
  }
}

export default Counter;

count๋ผ๋Š” state์™€ state๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” increment ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

๋ Œ๋”๋ถ€๋ถ„์—๋Š” ๋ถ€๋ชจ์ปดํฌ๋„ŒํŠธ์—์„œ ์–ธ๊ธ‰ํ•œ render๋ผ๋Š” props๋ฅผ ๋ฐ›์•„ ์ธ์ž๋กœ count์™€ increment๋ฅผ ํ• ๋‹นํ•ด์ค€๋‹ค.

๋ถ€๋ชจ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

App.js

import React from "react";
import Counter from "./components/Counter";
import ButtonCounter from "./components/ButtonCounter";


// prop์œผ๋กœ ๋ Œ๋”๋งํ•  ๋‚ด์šฉ์„ ์ „๋‹ฌ?
const App = () => {
  return (
    <div>
      <Counter
        render={(count, increment) => (
          <ButtonCounter count={count} increment={increment} />
        )}
      />
    </div>
  );
};

export default App;

๋ถ€๋ชจ์ปดํฌ๋„ŒํŠธ(App.js)์—์„œ๋Š” Counter ์ปดํฌ๋„ŒํŠธ์— render๋ผ๋Š” props๋ฅผ ์ „๋‹ฌํ•ด์ฃผ๊ณ  ๋‚ด์šฉ์€ ๋‘๊ฐœ์˜ ์ธ์ž๋ฅผ ๊ฐ–๋Š” ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์ค€๋‹ค.

ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜๊ฐ’์€ state๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š” ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ(ButtonCounter)๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

ButtonCounter์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

ButtonCounter.js

import { Component } from "react";

class ButtonCounter extends Component {
  render() {
    const { count, increment } = this.props;

    return <button onClick={increment}>Clicked {count} times!</button>;
  }
}

export default ButtonCounter;

์ด์ฒ˜๋Ÿผ ๋ถ€๋ชจ์ปดํฌ๋„ŒํŠธ์—์„œ props๋กœ ๋ Œ๋”ํ•  ๋‚ด์šฉ์„ ๋™์ ์œผ๋กœ ์ „๋‹ฌํ•ด์ฃผ์–ด ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ state sharing์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“™ ๋งˆ๋ฌด๋ฆฌ

HOCs์™€ render props ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋‹ค ์ •ํ•ด์ง„ API๊ฐ€ ์•„๋‹Œ ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋“ค์–ด์„œ ์“ฐ๋Š” ํŒจํ„ด์ด๋‹ค.

ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ state๋ฅผ ์ „๋‹ฌํ•  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งŽ์ง€ ์•Š๋‹ค๋ฉด ์ง์ ‘์ ์œผ๋กœ state๋ฅผ props๋กœ ์ „๋‹ฌํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋˜์ง€๋งŒ ์ „๋‹ฌํ•  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งŽ๋‹ค๋ฉด ์œ„ ๋‘๊ฐ€์ง€ ๋กœ์ง์„ ์ด์šฉํ•˜์—ฌ ํšจ์œจ์ ์œผ๋กœ state๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ๊ฒ ๋‹ค.

ํ•˜์ง€๋งŒ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋Ÿฌํ•œ ์ ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ HOOk์ด๋ผ๋Š” API๋ฅผ ์ œ์‹œํ•˜์˜€๊ณ  ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ์ ๋“ค์„ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฐธ์กฐ : stateful logic sharing

0๊ฐœ์˜ ๋Œ“๊ธ€