프로덕션에서 React Microfrontends의 놀라운 성능 교훈

Epilot 엔지니어링 팀은 대부분* Reactmicrofrontends를 기반으로 재작성된 포털을 출시한 지 1년 후에 27명의 개발자로 구성되었습니다.



*우리 앱의 일부는 다른 프런트엔드 프레임워크, 특히 Svelte로 작성된 사이드바 탐색을 사용하여 작성되었습니다.

1년 전 초기 출시 이후 우리 팀은 single-spa을 사용하여 생산 환경에서 React 마이크로프론트엔드를 실행하면서 많은 경험을 얻었습니다.

우리는 새로운 프런트엔드 마이크로서비스 아키텍처로 문제에 직면할 것으로 예상했지만 몇 가지 초기 문제를 해결한 후 첫해에 단일 스파로 큰 걸림돌을 치지 않았습니다.

놀랍게도 우리 코드베이스에서 나타난 대부분의 문제는 마이크로프론트엔드 아키텍처에만 국한되지 않는 일반적인 React 문제점입니다.

지식을 공유하기 위한 노력의 일환으로 이 게시물에서 우리 팀에서 다시 나타난 가장 일반적인 React 성능 문제를 다룰 것입니다.

상태 관리 문제



다음은 대부분의 React 마이크로프론트엔드 프로젝트에서 한 지점에서 나타나는 매우 일반적인 후크 패턴입니다.

// useFormState.jsx
import React from 'react'

const FormContext = React.createContext()

export const GlobalFormStateProvider = (props) => {
  const [formState, setFormState] = React.useState({})

  return (
    <FormContext.Provider value={{ formState, setFormState }}>
      {props.children}
    </FormContext.Provider>
  )
}

export const useFormState = () => React.useContext(FormContext)



// App.jsx
import { GlobalFormStateProvider } from './useFormState'
import { Form } from './Form' 

export const App = () => (
  <GlobalFormStateProvider>
    <Form />
  </GlobalFormStateProvider>
}



// Form.jsx
import React from 'react'
import { useFormState } from './useFormState'
import { api } from './api'

export const Form = () => (
  const { formState } = useFormState() 

  const handleSubmit = React.useCallback(
    () => api.post('/v1/submit', formState),
    [formState]
  )

  return (
    <form onSubmit={handleSubmit}>
      <FirstFormGroup />
      <SecondFormGroup />
    </form>
  )
)

const FirstFormGroup = () => (
  const { formState, setFormState } = useFormState()

  return (
    <div className="form-group">
      <input
        value={formState.field1}
        onChange={(e) => 
          setFormState({ ...formState, field1: e.target.value })}
      />
      <input
        value={formState.field2}
        onChange={(e) => 
          setFormState({ ...formState, field2: e.target.value })}
      />
    </div>
  )
)

const SecondFormGroup = () => (
  const { formState, setFormState } = useFormState()

   return (
    <div className="form-group">
      <input
        value={formState.field3}
        onChange={(e) => 
          setFormState({ ...formState, field3: e.target.value })}
      />
    </div>
  )
)


많은 독자는 위의 예에서 반패턴을 즉시 인식하지만 순진한 관점을 즐깁니다.
useFormState() 후크는 매우 유용합니다. 소품 드릴링이 없습니다. 멋진 글로벌 상태 관리 라이브러리가 필요하지 않습니다. 전역 컨텍스트에서 공유되는 네이티브React.useState()만 있습니다.

여기서 사랑하지 않는 것은 무엇입니까?

성능 문제


useFormState()가 좋아 보이지만, 이를 사용하는 구성 요소가 매번 setFormState() 렌더링해야 하므로 불필요하고 잠재적으로 비용이 많이 드는 재렌더링으로 인해 성능 문제에 빠르게 직면하게 됩니다.

이는 FormContext 내부의 React.useContext(FormContext)를 사용하여 useFormState()의 모든 변경 사항을 다시 렌더링하기 위해 모든 양식 구성 요소를 구독했기 때문입니다.
React.memo가 도움이 된다고 생각할 수 있지만 React docs을 읽으면 다음과 같습니다.

When the nearest above the component updates, this Hook will trigger a re-render with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a re-render will still happen starting at the component itself using useContext.



또한 모든 양식 구성 요소의 전체formState 객체에 불필요하게 의존하고 있습니다.

고려하다:

// formState is a dependency:
setFormState({ ...formState, field1: e.target.value })}
// formState not a dependency:
setFormState((formState) => ({ ...formState, field1: e.target.value }))


현재 복잡한 글로벌 앱 상태를 일반적인 React 성능 안티패턴으로 저장하기 위해 React.useState를 사용하는 컨텍스트 제공자를 고려할 것입니다.

그러나 React가 useContextSelector ( RFC )를 추가하면 상황이 바뀔 수 있다고 확신합니다. 🤞

교훈



상당히 경험이 많은 프론트엔드 개발자(React의 5년 이상)가 있는 React 프로젝트에서 이와 같은 안티패턴이 나타나는 것을 보면 일반적으로 React로 작업할 때 품질 출력을 생성하기 위해 불행히도 상당한 투자가 필요한 주제로 성능을 고려하게 되었습니다.

언제나처럼 No Silver Bullet이 있습니다. 그러나 우리의 프런트엔드 마이크로서비스 아키텍처를 통해 양식 성능을 해결하기 위해 꽤 많은 경쟁 전략을 생성한 여러 팀에서 다양한 접근 방식을 저렴한 비용으로 실험할 수 있었습니다.
  • 글로벌 상태 관리 라이브러리 사용 예: Redux , MobXXState .
  • 전용 양식 라이브러리 사용. react-hook-form
  • useContextSelector의 이 구현 사용
  • 제어된 양식 입력 방지(웹 플랫폼 활용! 👐)

  • 또한 single-spa의 유연성 덕분에 Svelte과 같은 프레임워크를 사용하여 React 생태계 외부에서 실험할 수 있었고 이는 엔지니어에게 매우 유망하고 보람이 있었습니다.



    We're hiring @ epilot!

    좋은 웹페이지 즐겨찾기