개발정보

Mbox React 통합

쿠카곰돌이 2023. 1. 9. 23:16
반응형

사용 방법:

import { observer } from "mobx-react-lite" // 또는 "mobx-react".
const MyComponent = observer(props => ReactElement)

MobX는 React와 독립적으로 작동하지만, 일반적으로 React와 함께 사용합니다. MobX의 요점에서 이미 통합에 가장 중요한 부분을 확인했습니다. 바로 React 컴포넌트를 감쌀 수 있는 observer HoC입니다.

observer는 설치 중에 선택한 별도의 React 바인딩 패키지에서 제공됩니다. 아래 예시에서는 더 가벼운 mobx-react-lite 패키지를 사용할 것입니다.

import React from "react"
import ReactDOM from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react-lite"

class Timer {
    secondsPassed = 0

    constructor() {
        makeAutoObservable(this)
    }

    increaseTimer() {
        this.secondsPassed += 1
    }
}

const myTimer = new Timer()

// `observer`로 감싸진 함수 컴포넌트는
// 이전에 사용했던 observable의 향후 변경 사항에 반응합니다.
const TimerView = observer(({ timer }) => <span>Seconds passed: {timer.secondsPassed}</span>)

ReactDOM.render(<TimerView timer={myTimer} />, document.body)

setInterval(() => {
    myTimer.increaseTimer()
}, 1000)

 

Hint: 위의 예제는 CodeSandbox에서 직접 실행해 볼 수 있습니다.

The observer HoC는 렌더링 중에 사용되는 모든 observable에 React 컴포넌트들을 자동으로 구독합니다. 결과적으로 관련 observable이 변경되면 컴포넌트들을 자동으로 다시 렌더링합니다. 또한 관련된 변경사항이 없을 때는 컴포넌트가 다시 렌더링 되지 않습니다. 따라서, 컴포넌트로부터 접근할 수는 있지만 실제로 읽지 않는 observable은 다시 렌더링 되지 않습니다.

이러한 로직은 MobX 어플리케이션을 즉시 최적화 시키며 과도한 렌더링을 방지하기 위해 추가 코드를 작성할 필요가 없습니다.

observer가 작동하려면 observable이 컴포넌트에 어떻게 도착하는지는 중요하지 않고 읽히기만 하면 됩니다. observable을 깊게 읽는 것도 잘 작동하고, todos[0].author.displayName처럼 복잡한 표현도 잘 작동합니다. 이러한 로직은 데이터 의존성을 명시적으로 선언하거나 미리 계산해야 하는 다른 프레임워크(selectors)에 비해 구독 메커니즘(mechanism)을 훨씬 더 정확하고 효율적으로 만듭니다.

로컬 및 외부 state

state를 구성하는 방법에는 큰 유연성이 있습니다. 왜냐하면 어떤 observable을 읽는지 또는 observable이 어디에서 유래했는지는 중요하지 않기 때문입니다. 아래 예제는 observer로 감싸인 컴포넌트에서 외부 및 로컬 observable state를 사용하는 방법에 대해 다양한 패턴을 보여줍니다.

observer 컴포넌트에서 외부 state 사용하기

props 사용
전역 변수 사용
React context 사용

observable는 props로 컴포넌트에 전달할 수 있습니다. (아래 예시 처럼)

import { observer } from "mobx-react-lite"

const myTimer = new Timer() // 위의 타이머 정의를 참고하세요.

const TimerView = observer(({ timer }) => <span>Seconds passed: {timer.secondsPassed}</span>)

// myTimer를 prop으로 전달합니다.
ReactDOM.render(<TimerView timer={myTimer} />, document.body)

observer컴포넌트에서 로컬 observable state 사용하기

observer가 사용하는 observable은 어디에서나 올 수 있으므로 로컬 state일 수도 있습니다. 다시 말해, 위에서 소개드린 옵션과는 다른 옵션도 사용할 수 있습니다.

observable 클래스와 `useState` 함께 쓰기
로컬 observable 객체와 `useState` 함께 쓰기
`useLocalObservable` hook

로컬 observable state를 사용하는 가장 간단한 방법은 useState를 사용하여 observable 클래스에 대한 참조를 저장하는 것입니다. 일반적으로 참조를 변경하는 경우는 드물기 때문에 useState에서 반환된 업데이터 함수를 완전히 무시합니다.

import { observer } from "mobx-react-lite"
import { useState } from "react"

const TimerView = observer(() => {
    const [timer] = useState(() => new Timer()) // 위의 타이머 정의를 참고하세요.
    return <span>Seconds passed: {timer.secondsPassed}</span>
})

ReactDOM.render(<TimerView />, document.body)

이전 예제에서 했던 것 처럼 타이머를 자동으로 업데이트하려면 useEffect를 일반적인 React방식으로 사용할 수 있습니다.

useEffect(() => {
    const handle = setInterval(() => {
        timer.increaseTimer()
    }, 1000)
    return () => {
        clearInterval(handle)
    }
}, [timer])

observable state가 로컬로 필요하지 않을 수 있습니다.

이론적으로 React's Suspense 메커니즘의 일부 기능을 차단할 수 있으므로 로컬 컴포넌트 state에 대해 MobX observable을 너무 빨리 의존하지 않는 것이 좋습니다. 일반적으로 state가 컴포넌트(하위항목 포함)간 공유되는 도메인 데이터를 캡쳐할 때 MobX observable을 사용하세요. ex) todo items, users, bookings 등등

로딩 state, 선택 등과 같은 UI state만 캡쳐하는 state는 useState hook을 사용하는 것이 더 좋습니다. 그렇게 하면 추후에 React suspense 기능을 사용할 수 있게 됩니다.

React 컴포넌트 안에서 observable을 사용하는 것은 깊거나(deep), computed 값이 있거나, 다른 observer 컴포넌트와 공유될 때 가치가 있습니다.

항상 observer 컴포넌트 안에서 observable을 읽습니다.

observer를 언제 사용해야 할지 궁금하시나요? 일반적으로 observable 데이터를 읽는 모든 컴포넌트에 observer를 사용합니다.

observer는 감싸고 있는 컴포넌트만 개선하며, 감싸고 있는 컴포넌트를 호출하는 컴포넌트는 개선하지 않습니다. 따라서 일반적으로 모든 컴포넌트는 observer에 의해 감싸져야 하며, 모든 컴포넌트를 observer로 감싸는 행동은 비효율적이지 않기 때문에 걱정하실 필요가 없습니다. observer 컴포넌트가 많을수록 업데이트의 세밀성이 더 높아져 렌더링 효율성이 높아집니다.

Tip: 가능한 한 늦게 객체에서 값을 가져옵니다.

observer는 가능한 한 오랫동안 객체 참조를 전달할 때, 그리고 DOM과 low-level 컴포넌트로 렌더링 될 예정인 observer 기반 컴포넌트 내부의 속성만 읽을 때 가장 잘 동작합니다. 즉, observer는 객체에서 값을 '역참조'한다는 사실에 반응합니다.

상단의 예시에서 TimerView 컴포넌트는 .secondsPassed가 observer컴포넌트 내부에서 읽는 것이 아니라 외부에서 읽혀 추적되지 않기 때문에 향후 변경사항에 반응하지 않습니다.

const TimerView = observer(({ secondsPassed }) => <span>Seconds passed: {secondsPassed}</span>)

React.render(<TimerView secondsPassed={myTimer.secondsPassed} />, document.body)

이러한 방법은 react-redux와는 다른 사고 방식입니다. react-redux에서는 메모이제이션을 더 잘 활용하기 위해 초기에 역참조하고 원시적인 값(primitives)을 전달하는 것이 더 좋은 관행입니다. 자세한 사항은 반응성 이해하기를 확인해주세요.

observer가 아닌 컴포넌트에 observable을 전달하지 마세요.

observer로 감싸진 컴포넌트는 컴포넌트 자체 렌더링 중에 사용되는 observable만 구독합니다. 따라서 observable objects·arrays·maps이 자식 컴포넌트에 전달되면 자식 컴포넌트들도 observer로 감싸줘야 합니다. 위의 내용은 모든 콜백 요소 기반 컴포넌트들도 해당합니다.

observer가 아닌 컴포넌트에 observable을 전달하려는 경우엔 전달하기 전에 observable을 일반 Javascript 값 또는 구조로 변환 해야합니다.

위의 내용을 자세히 설명하기 위해 observable todo 객체, TodoView 컴포넌트 (observer), 열(column)과 값(value) 매핑을 사용하지만, observer가 아닌 가상의 GridRow 컴포넌트를 예로 들어보겠습니다.

class Todo {
    title = "test"
    done = true

    constructor() {
        makeAutoObservable(this)
    }
}

const TodoView = observer(({ todo }: { todo: Todo }) =>
   // 잘못된 예시: GridRow는 observer가 아니기 때문에 todo.title 과 todo.done에 대한 변경 사항을 선택하지 않습니다.
   return <GridRow data={todo} />

   // 올바른 예시: `TodoView`가 `todo` 관련 변경사항을 감지하여
   //            일반 데이터를 전달하도록 합니다. 
   return <GridRow data={{
       title: todo.title,
       done: todo.done
   }} />

   // 올바른 예시: `toJS`를 사용하는 것도 좋지만, 일반적으로는 명시적으로 사용하는 것이 더 좋습니다.
   return <GridRow data={toJS(todo)} />
)

콜백 컴포넌트는 <Observer>가 필요할 수 있습니다.

GridRow가 onRender 콜백을 사용하는 동일한 예를 상상해보세요. onRender는 TodoView의 렌더가 아니라 GridRow의 렌더링 주기의 일부이기 때문에 (구문상 표시가 되는 위치에 있음에도 불구하고) 콜백 컴포넌트가 observer 컴포넌트를 사용하는지 확인해야 합니다. 또는 <Observer />을 사용하여 인라인 익명 observer를 만들 수 있습니다.

const TodoView = observer(({ todo }: { todo: Todo }) => {
    // 잘못된 예시: GridRow.onRender는 observer가 아니기 때문에 todo.title / todo.done의 변경사항을 선택하지 않습니다.
    return <GridRow onRender={() => <td>{todo.title}</td>} />

    // 올바른 예시: 변경사항을 감지 할 수 있도록 Observer에 콜백 렌더링을 감쌉니다.
    return <GridRow onRender={() => <Observer>{() => <td>{todo.title}</td>}</Observer>} />
})

Tips

Server Side Rendering (SSR)Note: mobx-react vs. mobx-react-lite

  1.  
  2.  

 

Note: observer 또는 React.memo?Tip: 클래스 기반 리액트 컴포넌트를 위한 observer 사용방법


 

Tip: React DevTools에서 멋진 컴포넌트 이름 사용하기


  •  
  •  
  • 
    
  • 
    


  •  

{🚀} Tip: observer를 다른 고차 컴포넌트와 결합할 때 observer를 먼저 적용하세요.

 

{🚀} Tip: props로부터 computed 파생


 

{🚀} Tip: useEffect 와 observable

 


 

 

React 컴포넌트를 어떻게 최적화할 수 있나요?

React 최적화 {🚀}를 확인해주세요.

문제해결 방법

도와주세요. 컴포넌트가 리렌더링 되지 않아요...

  1. observer를 잊지 않았는지 확인하세요.
  2. 반응하려는 대상이 실제로 observable인지 확인해보세요. 런타임에 확인할 경우 isObservable, isObservableProp와 같은 유틸리티를 사용해보세요.
  3. 브라우저의 콘솔 로그에 경고 또는 에러가 있는지 확인해보세요.
  4. 일반적으로 추적이 어떻게 작동하는지 확인해보세요. 반응성 이해하기를 참고하세요.
  5. 위에서 설명하고 있는 잘못된 예시를 확인해보세요.
  6. 잘못된 메커니즘 사용에 대해 경고하고 콘솔 로그를 확인할 수 있도록 MobX를 설정하세요.
  7. trace를 사용하여 올바른 구독을 하고 있는지 확인하거나 spy, mobx-logger 패키지를 사용하여 MobX가 일반적으로 무엇을 하는지 확인해보세요.

 

 

반응형