본문 바로가기

React/study

[React] #03 Context API에 대하여

App의 규모가 커지면 커질수로 컴포넌트는 많아진다. 작업을 하다보면 컴포넌트간 연결을 해야할 경우가 꼭 생긴다! 하지만 React는 기본적으로 단방향 데이터 흐름으로 진행이 되고 있다.  먼저 시작전에 단방향 데이터 흐름에 대한 이미지를 확인해보자

 

위의 흐름도 처럼 컴포넌트에서 컴포넌트로 데이터를 넘겨줄때 다양한 데이터를 넘겨 줄수 있다 object, array, function이 대표적이다. 하지만 규모 커지면 컴포넌트가 엄청 많아지게 된다고 했다 만약 어떠한 정보를 팝업으로 처리한다고 가정하자. 이럴때 완료를 눌러 팝업을 닫음과 동시에 컴포넌트를 업데이트 해줘야 한다. 하지만, 단방향 흐름이기 때문에 다시 처음부터 렌더링이 시작이 된다 이것은 굉장히 비효율적이다! 또한, 렌더링이 많이질수록 APP의 퍼포먼스는 떨어진다 이럴때 사용하는 것이 ContextAPI다!

 

위의 이미지 처럼 컴포넌트에서 이벤트가 발생하여 해당 컴포넌트만 업데이트를 시킬수 있는 것이다.

한두단계일 경우는 굳이 contextAPI 사용할 필요가 없다 적절하게 사용하자

 

자 이제 코드를 세팅해보자!

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

// context file import
import { ContextProvider } from './contexts/UserContext'

ReactDOM.render(
  <ContextProvider value={{ context:'TEST_CONTEXT_VALUE' }}>
    <App />
  </ContextProvider>, 
  document.getElementById('root')
);

 

src/contexts/UserContext.js

import * as React from 'react';

const Context = React.createContext();
const {
  Provider,
  Consumer : ContextConsumer
} = Context;

class ContextProvider extends React.Component {
  state = {
    context: this.props.value.context
  }

  render() {
    const { state, actions } = this;
    console.log(this)
    return (
      <Provider value={{ context: state.context }}>
        {this.props.children}
      </Provider>
    )
  }
}

export {
  ContextProvider,
  ContextConsumer
}

console.log(this)를 보면 Context의 데이터를 확인할수 있다

Provider   : Context에서 사용 할 값을 설정할 때 사용 또는 전달하는 데이터
Consumer : 설정한 값을 불러와야 할 때 사용 또는전달받은 데이터를 사용

 

src/App.js

import React from 'react';
import { ContextConsumer } from './contexts/UserContext'

function App() {
  return (
    <div className="App">
      HELLO!
      <ContextApiViewTest />
    </div>
  );
}

function ContextApiViewTest () {
  return (
    <ContextConsumer>
      {({ context }) => (
          <div>
            {context}
          </div>
      )}
    </ContextConsumer>    
  )
}

export default App;

코드를 보면 Provider는 index.js에서 설정을 했다 그리고 App.js에서 ContextConsumer를 불러화 context라는 Parameter를 확인보면 index.js에서 설정한 'TEST_CONTEXT_VALUE' 텍스트가 나온다

 


좀더 유연하게 사용해보자! 위의 방법으로 context에 대한 디버깅이 어렵다 Props에 대한 처리를 하기 위해선 HOC 패턴을 이용해 ContextAPI 수정할 수 있다!

 

src/contexts/UserContext.js

import * as React from 'react';

const Context = React.createContext();
const {
  Provider,
  Consumer
} = Context;

class ContextProvider extends React.Component {
  state = {
    context: this.props.value.context
  }

  render() {
    const { state, actions } = this;
    console.log(this)
    return (
      <Provider value={{ context: state.context }}>
        {this.props.children}
      </Provider>
    )
  }
}

// :: HOC
function ContextConsumer(WrappedComponent) {
  return class extends React.Component {
    render() {
      return (
        <Consumer>
          { ({ context }) => <WrappedComponent context={context} /> }
        </Consumer>
      )
    }
  }
}

export {
  ContextProvider,
  ContextConsumer
}

 

src/App.js

import React from 'react';
import { ContextConsumer } from './contexts/UserContext'

class App extends React.Component {
  render(){
    return (
      <div className="App">
        HELLO!
        <ContextApiViewTest />
      </div>
    );
  }
}

class ContextApiViewTest extends React.Component {
  render(){
    console.log(this)
    return (
      <div>
        {this.props.context}
      </div>
    );
  }
}

ContextApiViewTest = ContextConsumer(ContextApiViewTest);

export default App;

위의 코드 방법으로 사용하면 <ContextApiViewTest /> 컴포넌트에 Props를 받아 유연하게 사용이 가능하다

아래와 같이 연결되어있는 않은 컴포넌트에서 Event 발생 시 감지하여 값을 변경하는 샘플코드를 작성해보겠다

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ContextProvider } from './contexts/UserContext'

ReactDOM.render(
  <ContextProvider>
    <App />
  </ContextProvider>, 
  document.getElementById('root')
);

src/context/UserContext.js

import * as React from 'react';

const Context = React.createContext();
const { Provider, Consumer } = Context;

class ContextProvider extends React.Component {
  state = {
    context: 'INITIAL'
  }

  actions = {
    contextAction: (context) => {
      this.setState({context});
    }
  }

  render() {
    const { state, actions } = this;
    const value = { state, actions };
    return (
      <Provider value={value}>
        {this.props.children}
      </Provider>
    )
  }
}

// :: HOC
function ContextConsumer(WrappedComponent) {
  return class extends React.Component {
    render() {
      return (
        <Consumer>
          {({ state, actions }) => (
            <WrappedComponent 
              context={state.context} 
              setValue={actions.setValue}
            /> 
          )}
        </Consumer>
      )
    }
  }
}

export { ContextProvider, ContextConsumer }

actions라는 함수가 추가가 되었다 이 함수는 어떤한 이벤트 발생 시에 context안의 state값을 변경한다.

그러면 변경된 값이 Consumer로 전달되서 props로 받을수 있다.

 

src/context/App.js

import React from 'react';
import { ContextConsumer } from './contexts/UserContext'

class App extends React.Component {  
  render(){
    console.log('UPDATE')
    return (
        <div className="App">          
          <ContextApiViewTest />
          <InChecker />
        </div>
    );
  }
}

class ContextApiViewTest extends React.Component {
  handle = () => {    
    this.props.contextAction( "CHANGE" );
  }

  render(){
    console.log(this)
    return (
      <div>
        <button onClick={this.handle}>CLICK</button>
      </div>
    );
  }
}

class InChecker extends React.Component{
  render() {
    console.log(this)
    return(
      <div>
        {this.props.context}
      </div>
    )
  }
}

InChecker = ContextConsumer(InChecker);
ContextApiViewTest = ContextConsumer(ContextApiViewTest);

export default App;

<ContextApiViewTest /> 의 컴포넌트의 버튼을 클릭하면 <InChecker />의 값이 변경되는 것을 확인할수 있다.

<App /> 컴포넌트는 전혀 렌더가 되지 않는다 규모가 커지면 연결된 컴포넌트가 무수히 많아진다 이럴때 컴포넌트간 이벤트 연결을 쉽게 할수있다. 대체적으로 JSON, Array를 주로 받을것으로 예상한다

 

이런 부분을 상태관리 툴이라고 이야기를 한다 대표적으로 Redux, Mobx가 있다

프로젝트의 성격에 맞게 원하는 유틸을 사용하면 된다