본문 바로가기

카테고리 없음

[번역] UI is a function of state

 

UI is a function of state

My very own corner of the web

www.kn8.lt

 

React는 UI 개발에 혁명을 일으켰습니다.

이것이 최종 패러다임이 아닐 수도 있고 새로운 아이디어를 탐구하지 말아야 한다는 뜻은 아닙니다. 하지만 어쨋든 React는 멋지고 사용하기 매우 재밌습니다. 

 

React의 핵심 원칙은 전체 UI가 일부 상태 (또는 단순히 데이터)를 취하고 렌더링된 전체 사용자 인터페이스를 반환하는 함수라는 것입니다.

UI = fn(state)

 

이 아이디어를 적용하기 위해 React가 필요하지도 않습니다. React를 도입하지 않고 좀 더 자세히 살펴봅시다. 이러한 UI 함수를 구현하고 렌더링된 콘텐츠를 페이지에 삽입해 봅시다.

 

const Header = () => `
  <div>Logo</div>
`

const Counter = state => `
  <h1>Count: ${state.count}</h1>
`

const App = state => `
  <div id='App'>
    ${Header()}
    ${Counter(state)}
  </div>
`
document.body.innerHTML = App({ count: 5 })

 

이게 다입니다. 다른 상태로 앱을 렌더링하고 싶다면 App 함수를 다시 호출하세요. 이것이 React의 핵심 개념입니다. 하지만 React는 실제 상황에서 훨씬 더 많은 도움을 줍니다.

 

- 사용자 이벤트를 처리하는데 도움을 줍니다.

- 컴포넌트를 쉽게 중첩할 수 있습니다.

- 컴포넌트가 추가되거나 제거될 때를 위한 훅이 있습니다.

- 그 밑에 가상 돔을 사용해서 이 모든 것을 매우 효율적으로 만듭니다.

- 컴포넌트를 매우 효과적으로 재사용할 수 있도록 도와줍니다.

 

이제 작은 예제 앱을 다시 React로 작성해봅시다. 각각 상태 조각을 사용하고 잠재적으로 복잡하고 유용한 동작을 캡슐화하는 많은 React 컴포넌트로 전체 애플리케이션을 구성하는 것을 상상할 수 있습니다. 

 

const App = ({ user, repo, org }) => (
  <Container>
    <Header user={user} />
    <RepoHeader org={org} repo={repo} />
    <Tabs>
      <Button>Code</Button>
      <Button>Issues</Button>
      <Button>PullRequests</Button>
    </Tabs>
    <Description />
    <RepoSummaryBar />
    <FileBrowser repo={repo} />
    <Readme />
  </Container>
)
ReactDOM.render(<App {...state} />, document.body)

 

App이 여전히 상태를 입력으로 받아 전체 UI를 출력하는 함수임을 알 수 있습니다. 이것이 왜 중요할까요? 간단하고 예측 가능하기 때문입니다. 애플리케이션의 각 작은 부분을 또 다른 함수로 생각하면 됩니다. 그리고 이 모든 함수는 순수하고 상태가 없습니다. 몇 가지 인수를 받고 몇가지 UI를 반환합니다. 이러한 각 함수에 대해 추론하는 것은 쉽습니다. 

Changing State

지금까지 살펴본 내용은 모두 훌륭하고 좋습니다. 하지만 한가지 문제가 있습니다. 실제 애플리케이션에서는 상태가 항상 바뀝니다. 

 

- 서버 상태 변경, e.g. someone posts a comment

- UI 상호 작용, e.g. you click "Merge PR"

- VR 상호 작용, e.g. you turn your head

- 사이드 이펙트, e.g. a game engine tick updates the world state

 

UI를 어떻게 최신 상태로 유지하나요?

 

정적인 방법:

 

ReactDOM.render(<App state={state} />, document.body)

 

이를 계속 호출할 수 있습니다.

 

function render(state) {
  ReactDOM.render(<App state={state} />, document.body)
}

 

상태를 업데이트하고 UI를 다시 렌더링하는 업데이트 함수를 만들면 어떨까요? 그런 다음 이 업데이트 함수를 컴포넌트에 전달하면 컴포넌트는 상태를 업데이트해야 할 때마다 이 함수를 호출할 수 있습니다. 

 

let state = { count: 0 }

function update (nextState) {
  state = Object.assign({}, state, nextState)
  render()
}

const App ({ state, update }) => (
  <div onClick={() => update({ count: state.count + 1 })}>
    <div>Count: {state.count}</div>
  </div>
)

function render () {
  ReactDOM.render(<App state={state} update={update} />)
}

render()

 

일부 앱 컴포넌트가 상태를 업데이트해야할 때마다 업데이트 함수를 사용하여 업데이트할 수 있습니다. 예를 들어 사용자가 버튼을 클릭한 후 카운터를 증가시킬 수 있습니다. 또는 다른 컴포넌트가 렌더링 할 서버 응답을 저장할 수도 있습니다. 어디서든 업데이트 함수를 호출하면 전체 애플리케이션이 새로운 상태로 다시 렌더링됩니다. 

 

이러한 단일 상태 저장소 접근 방식은 리덕스나 작은 원자 같은 라이브러리를 효과적으로 활용할 수 있도록 도와줍니다. 이 같은 예는 tiny-atom을 사용하면 다음과 같이 보일 것입니다:

 

const atom = createAtom(
  { count: 0 },
  {
    increment: ({ get, set }) => {
      const currCount = get().count
      set({ count: currCount + 1 })
    }
  }
)

const Counter = () => {
  const count = useAtom(state => state.count)
  const { increment } = useActions()
  return (
    <>
      Count: {count}
      <button onClick={increment}>Increment</button>
    </>
  )
}

const App = () => (
  <Provider atom={atom}>
    <Counter />
  </Provider>
)

ReactDOM.render(<App />, document.querySelector('root'))

 

이것은 우리가 이미 본 것과 동일한 원리입니다.

 

모든 상태를 저장할 장소가 있고, 이 상태를 업데이트하는 방법이 있으며, 상태가 변경될 때마다 다시 렌더링됩니다.(because Counter component is listening to store changes thanks to the useAtom hook)

 

실제 애플리케이션에서는 React 프로퍼티로 상태와 업데이트 또는 액션을 전달하는 대신 이 예시와 같은 헬퍼 함수를 사용하여 상태로부터 값을 읽는 것이 더 편리하고 효율적입니다. 

 

읽어주셔서 감사드리며, 이 아이디어를 처음 접하는 분들이 흥미로웠기를 바랍니다. 

 

UI development has never been so simple, thanks to React.