프로덕션에서 React Microfrontends의 놀라운 성능 교훈
*우리 앱의 일부는 다른 프런트엔드 프레임워크, 특히 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이 있습니다. 그러나 우리의 프런트엔드 마이크로서비스 아키텍처를 통해 양식 성능을 해결하기 위해 꽤 많은 경쟁 전략을 생성한 여러 팀에서 다양한 접근 방식을 저렴한 비용으로 실험할 수 있었습니다.
또한 single-spa의 유연성 덕분에 Svelte과 같은 프레임워크를 사용하여 React 생태계 외부에서 실험할 수 있었고 이는 엔지니어에게 매우 유망하고 보람이 있었습니다.
We're hiring @ epilot!
Reference
이 문제에 관하여(프로덕션에서 React Microfrontends의 놀라운 성능 교훈), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/epilot/surprising-performance-lessons-from-react-microfrontends-in-production-528a텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)