FrontEnd/[리액트 2부] 고급 주제와 훅

[2.1장 컨텍스트]

Tony Lim 2024. 3. 1. 11:32
<A>
	<B>
		<C>

프롭 드릴링의 문제

  • 값의 출처 파악 어려움 , 컴포넌트 중첩이 깊숙히 될수록
  • B는 A와 C 사이에 위치했다는 이유만으로 고유의 역할과 무관하게 메시지를 전달해야함

 

이벤트 에미터 패턴을 통해 해결 할 수 있다.

두 객체 간의 메시지를 비교적 자유롭게 주고 받을 수 있는 통로 역할이다.

const createEventEmitter = (value) => {
  let handlers = [];

  const on = (handler) => handlers.push(handler);
  const off = (handler) => {
    handlers = handlers.filter((h) => h !== handler);
  };

  const get = () => value;
  const set = (newValue) => {
    value = newValue;
    handlers.forEach((handler) => handler(value));
  };

  return {
    on,
    off,
    get,
    set,
  };
};

export default createEventEmitter;
const eventEmitter = createEventEmitter(0);
const logger = (value) => console.log("logger", value);

eventEmitter.on(logger);
console.log(eventEmitter.get());
eventEmitter.set(1);
eventEmitter.set(2);
eventEmitter.off(logger);
eventEmitter.set(3);
console.log(eventEmitter.get());

eventemitter는 closure를 사용한것이다. createEventEmitter를 생성할 때 0을 value로 주면 return 값으로 주는 on,off, get,set 함수들에는 value==0 인 상태의 lexical scope이 저장이 된다.

모든 함수는 함수가 선언이 될때의 lexical scope를 기억한다. 

get도 마찬가지로 내부함수로 선언되는 순간에 Environment에 현재 lexical scope인 value == 0이 저장이 된다.

set도 마찬가지이다. 그래서 예시 코드를 실행 시키면 의도한대로 초기값이 0인 상태로 잘 동작을 하게 된다.


컨택스트

const MyReact = (function () {
  function createContext(initialValue) {
    const emitter = createEventEmitter(initialValue);
    class Provider extends React.Component {
      componentDidMount() {
        emitter.set(this.props.value);
      }

      componentDidUpdate() {
        emitter.set(this.props.value);
      }
      render() {
        return <>{this.props.children}</>;
      }
    }
    class Consumer extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          value: emitter.get(),
        };
        this.setValue = this.setValue.bind(this);
      }

      setValue(nextValue) {
        this.setState({ value: nextValue });
      }

      componentDidMount() {
        emitter.on(this.setValue);
      }

      componentWillUnmount() {
        emitter.off(this.setValue);
      }
      render() {
        return <>{this.props.children(this.state.value)}</>;
      }
    }

    return {
      Provider,
      Consumer,
    };
  }
  return {
    createContext,
  };
})();

export default MyReact;

 

Consumer에서는 componetdidMount에서  setState를 emitter 에 등록을 하고 

Provider 에서는 componentdidMount에서는 context를 emitter에 set을 호출하면서 Consumer가 등록한 handler
호출해준다. 

내부 component부터 compoentDidMount가 호출이 된다.

const countContext = MyReact.createContext({
  count: 0,
  setCount: () => {},
});

class CountProvider extends React.Component {
  constructor(props) {
    console.log("CountProvider construtor");
    super(props);
    this.state = {
      count: 0,
    };
  }

  render() {
    const value = {
      count: this.state.count,
      setCount: (nextValue) => this.setState({ count: nextValue }),
    };
    return (
      <countContext.Provider value={value}>
        {this.props.children}
      </countContext.Provider>
    );
  }
}

const Count = () => {
  return (
    <countContext.Consumer>
      {(value) => {
        console.log("CountComponent", value);
        return <div>{value.count}</div>;
      }}
    </countContext.Consumer>
  );
};

const PlusButton = () => {
  return (
    <countContext.Consumer>
      {(value) => {
        console.log("PlustButtonComponent", value);
        return (
          <button onClick={() => value.setCount(value.count + 1)}>
            + 카운트 올리기
          </button>
        );
      }}
    </countContext.Consumer>
  );
};

export default () => (
  <CountProvider>
    <Count />
    <PlusButton />
  </CountProvider>
);

호출 순서는 위와 같다. Provider 생성자 -> Consumer 생성자
-> Consumer ComponentDidMount(emitter#on 에서 handler등록 ,Producer에서 제공해주는 value를 받기 위함이다. 초기에는 createContext에서 제공해준 value값을 받아오기 때문이다.)
-> Producer ComponentDidMount -> Producer의 componentDidMount에서 emitter#set 을 통해서 setValue가 호출이 된다.
이러면 Producer가 render하기 전에 생성한 value (count, setCount) 들이 Consumer에게도 update가 된다.

button을 클릭하면 PlusButton 의 onClick에 의해 CounterProvider의 state가 변경되면서 CountProvider가 re render된다.

이때  Producer도 re render된다.

이때 Provider의 하위 컴포넌트도 다 rerender가 된다. 

Provider componentDidUpdate가 호출되면서 emitter set을 호출해서 등록된 handler들을 호출한다. 이때 handler는 setValue로서 Consumer의 state가 변경이된다. 

즉 context의 provider에 value로 넣어준 값(state, setState)을 통해서 같은 context의 consumer들이 onClick을 통해provider의 state를 setState로 넣어준 값을 바뀌게 하면 Provider -> Conusmer re rendering이 된다.

https://www.inflearn.com/questions/1194482/2-1%EC%9E%A5-%EC%BB%A8%ED%83%9D%EC%8A%A4%ED%8A%B8-2-1-4-%EA%B3%B5%EA%B8%89%EC%9E%90%EC%99%80-%EC%86%8C%EB%B9%84%EC%9E%90-%EC%97%90%EC%84%9C-%EC%A7%88%EB%AC%B8%EC%9D%B4-%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4

 

[2.1장 컨택스트] 2.1.4 공급자와 소비자 / 에서 질문이 있습니다. - 인프런

안녕하세요 선생님 react context를 이해하려고 시도하는 중입니다.const countContext = MyReact.createContext({ count: 0, setCount: () => {},});class CountProvider extends R...

www.inflearn.com

컨텍스트가 해결할 수 있는 문제

  • 인접한 컴포넌트 간에만 인자를 전달할 수 있는 구조적 한계
  • 중간 컴포넌트 없이 비교적 직접 전달할 수 있는 방법이 리액트 컨택스트