리액트

#6. Component 개선하기(3) - 리액트 밖에서 상태 관리하기, Flux

YJH3968 2021. 3. 31. 15:07
728x90

1. 리액트 밖에서 상태 관리하기

  • 애플리케이션의 규모가 커질 경우 상태 데이터를 UI로부터 분리해서 상태만 처리하는 계층을 만드는 것이 더 나을 수도 있다.
  • 상태를 리액트 밖에서 관리할 경우 클래스 component를 상태가 없는 함수형 component로 바꿀 수 있어 보다 더 나은 함수형 애플리케이션이 될 수 있다.
  • 하나의 예시로 앞에서 다뤘던 시계를 log에 출력했던 startTicking 함수를 수정해 브라우저에 표시한다.
// 기존의 startTicking 함수
const startTicking = (() =>
    setInterval(
        compose(
            clear,
            getCurrentTime,
            abstractClockTime,
            convertToCivilianTime,
            doubleDigits,
            formatClock("hh:mm:ss tt"),
            display(log)
        ),
        oneSecond()
    )
startTicking()

// 시간을 보여주는 리액트 component
const AlarmClockDisplay = ({hours, minutes, seconds, ampm}) =>
    <div className="clock">
        <span>{hours}</span>
        <span>:</span>
        <span>{minutes}</span>
        <span>:</span>
        <span>{seconds}</span>
        <span>{ampm}</span>
    </div>

// 위 코드를 반영한 startTicking 함수 
const startTicking = (() =>
    setInterval(
        compose(
            clear,
            getCurrentTime,
            abstractClockTime,
            convertToCivilianTime,
            doubleDigits,
            formatClock("hh:mm:ss tt"),
            render(AlarmClockDisplay)
        ),
        oneSecond()
    )
startTicking()

// 화면을 렌더링할 고차 함수 render
const react = Component => clvilianTime =>
    ReactDOM.render(
        <Component {...civilianTime} />,
        <document.getElementById('react-container')
    )
  • 이 애플리케이션은 상태를 리액트 밖에서 관리해서 상태가 있는 component 클래스를 요구하지 않는다.

2. Flux

  • Flux는 데이터 흐름을 한 방향으로 유지하기 위해 페이스북에서 설계한 디자인 패턴이다.
  • 리액트가 UI를 만들기 위해 사용하는 데이터를 공급할 방법을 제공한다.
  • 애플리케이션 상태 데이터는 store에 저장해서 리액트 밖에서 관리한다. store는 데이터를 저장하고 변경하며 플럭스 내부에서 뷰를 갱신할 수 있는 유일한 존재다.
  • 예를 들어 사용자가 웹 페이지와 상호작용하는 작업을 했다고 가정하면 우선 사용자의 요청을 표현하는 action이 만들어지고, dispatcher라는 중앙 제어 component가 action을 가져와 분배한다.
  • action은 변화를 일으키는 명령과 데이터를 제공한다.
  • dispatcher는 action을 대기열에 넣거나 대기열에 있는 action을 빼서 적절한 store에 보내기 위해 만든 component다.
  • 데이터는 action -> dispatcher -> store -> view의 일정한 방향으로만 흐른다.
  • Flux의 action과 상태 데이터는 변경 불가능하다. action은 view로부터 dispatch되거나 웹 서버 등 다른 곳으로부터 도착할 수 있다.
  • 변경이 일어나려면 action이 필요하다. 모든 action은 변경 명령을 제공한다.
  • 이 패턴에는 아무 부수 효과도 일어나지 않는다.
  • 상태 변경이 일어나는 부분은 store뿐이다. store는 데이터를 갱신하고 view는 변경을 통지받아 UI에 렌더링한다. 그리고 action은 그런 변경이 어떻게 왜 이루어져야 하는지 알려준다.
  • 이러한 방식으로 애플리케이션의 데이터 흐름을 제한하면 애플리케이션을 고차거나 규모를 확장하기 더 쉬워진다.
  • 실제로 이러한 디자인 패턴을 이용한 countdown 애플리케이션을 작성해보자.
  • 먼저 뷰 영역을 나타내면 다음 코드와 같다.
// 상태가 없는 react component인 view
const CountDown = ({count, tick, reset}) => {
  if (count) {
    setTimeout(() => tick(), 1000)
  }

  return (count) ?
    <h1>{count}</h1> :
    <div onClick={() => reset(10)}>
      <span>축하합니다!!</span>
      <span>{처음부터 다시 시작하려면 클릭하세요}</span>
    </div>
}
  • action 생성기는 action을 만들 때 필요한 이런저런 사항을 추상화해주는 함수다.
  • action 자체는 type이라는 필드만 들어 있으면 되는 객체다. type은 그 action의 유형을 알려주는 문자열이며 보통 대문자로만 이루어진다.
// action
const countdowmActions = dispatcher =>
  ({
    tick(currentCount) {
      dispatcher.handleAction({ type: 'TICK '})
    },
    reset(count) {
      dispatcher.handleAction({
        type: 'RESET',
        count
      })
    }
  })
  • 위 코드에서 tick이나 reset method에서 dispatcher의 handleAction method에 TICK이나 RESET type의 action을 전달하면 action 객체를 dispatch(할당)한다.
  • dispatcher는 오직 하나만 존재하고 action을 받아서 그 action이 어디에서 만들어졌는지에 대한 정보를 덧붙인 다음 action을 적절한 store(또는 여러 store)로 보낸다. action을 받은 store는 그것을 적절히 처리한다.
// Dispatcher
import Dispatcher from 'flux'

class CountDownDispatcher extends Dispatcher {
  handleAction(action) {
    console.log('dispatching action:',action)
    this.dispatch({
      source: 'VIEW_ACTION',
      action
    })
  }
}
  • 모든 store는 생성 시 dispatcher에 자신을 등록하고 action의 도착을 listen한다. dispatch된 action은 도착한 순서대로 처리되며 적절한 store에 전달된다.
  • store는 애플리케이션의 로직과 상태 정보를 담는 객체다.
  • store의 현재 상태 데이터는 프로퍼티를 통해 얻을 수 있다. store가 상태 데이터를 변경하기 위해 필요한 모든 정보는 action 안에 들어 있어 type에 따라 action을 처리하고 상태를 갱신한다. 데이터가 바뀌는 경우 store는 이벤트를 발생시켜서 자신을 담고 있는 view에 데이터가 변경되었다는 사실을 통지한다.
// store
import { EventEmitter } from 'events'

class CountdownStore extends EventEmitter {
  constructor(count=5, dispatcher) {
    super()
    this._count = count
    this.dispatcherIndex = dispatcher.register(
      this.dispatch.bind(this)
    )
  }

  get count() {
    return this._count
  }

  dispatch(payload) {
    const { type, count } = payload.action
    switch(type) {
      case "TICK":
        this._count = this._count - 1
        this.emit("TICK", this._count)
        return true
      case "RESET":
        this._count = count
        this.emit("RESET", this._count)
        return true
    }
  }
}
  • 이 store는 카운트다운 애플리케이션의 상태인 count를 저장한다. count는 읽기 전용 프로퍼티를 통해 접근할 수 있다. action이 dispatch되면 store는 그 action을 사용해 count를 변경한다.
  • 상태가 바뀌고 나면 store는 자신을 listen하는 모든 view에 이벤트를 발생시킨다.
  • 이제 각 부분을 연결하면 다음과 같다.
// 각 부분을 연결한다.
const appDispatcher = new CountDownDispatcher()
const actions = countdownActions(appDispatcher)
const store = new CountdownStore(10, appDispatcher)

const render = count => ReactDOM.render(
  <Countdown count={count} {...action} />,
  document.getElementById('react-container')
)

store.on("TICK", () => render(store.count))
store.on("RESET", () => render(store.count))
render(store.count)
  • 위 코드의 마지막 부분은 store에 listener를 추가하는 과정으로 store가 TICK이나 RESET을 발생시키면 새로운 count 값이 생기고 즉시 view에 전달된다.
  • 플럭스 구현에는 여러 가지 방법이 있는데, 플럭스, 리플럭스, 플럼목스, 플럭서블, 리덕스, 몹엑스 등이 있다.'

출처 : learning react(2018)

728x90