一.개관
몇 가지 편리한 특성이 추가되었습니다:
-
React.memo: 함수형 컴포넌트에도 "shouldComponentUpdate" 라이프사이클이 생겼습니다 -
React.lazy: Suspense 특성과 배합하여 쉽게 우아하게 코드 분할 (Code-Splitting) 을 완료 -
static contextType: class 컴포넌트가 더 쉽게 단일 Context 에 액세스 가능 -
static getDerivedStateFromError(): SSR 프렌들리한 "componentDidCatch"
그 중에서 가장 중요한 것은 Suspense 특성으로, 이전의 React Async Rendering 에서 언급했습니다:
또한, 장래 suspense(서스펜드) API 가 제공되어, 뷰 렌더링을 서스펜드하고, 비동기 작업의 완료를 기다리며, loading 장면을 더 쉽게 제어할 수 있게 됩니다. 구체적으로는 Sneak Peek: Beyond React 16 강연 비디오의 2 번째 Demo 를 참조
그리고 현재 (v16.6.0, 2018/10/23 릴리스), 그것은 약 8 개월 후의 "장래" 입니다
二.React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
});
또한 옵션의 compare 파라미터가 있습니다:
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
PureComponent 와 유사한 고계 컴포넌트로, memo 를 한 층 감싸면, 일반 함수형 컴포넌트에 PureComponent 의 성능 우위를 갖게 할 수 있습니다:
React.Component doesn't implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.
내부 구현
구현은 매우 간단합니다:
export default function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
return {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare,
};
}
요컨대外挂式 shouldComponentUpdate 라이프사이클로, class 컴포넌트와 비교:
// 일반 class 컴포넌트
class MyClassComponent {
// 기본 shouldComponentUpdate 없음, 수동으로 구현 가능
shouldComponentUpdate(oldProps: Props, newProps: Props): boolean {
return true;
}
}
// PureComponent 로부터 상속한 컴포넌트는 동등
class MyPureComponent {
// 기본 shouldComponentUpdate 를 가짐, 즉 shallowEqual
shouldComponentUpdate(oldProps: Props, newProps: Props): boolean {
return shallowEqual(oldProps, newProps);
}
}
// 함수형 컴포넌트
function render() {
// 함수형 컴포넌트, shouldComponentUpdate 를 지원하지 않음
}
// Memo 컴포넌트는 동등
const MyMemoComponent = {
type: function render() {
// 함수형 컴포넌트, shouldComponentUpdate 를 지원하지 않음
}
// 기본의 (밖에 매달린) shouldComponentUpdate 를 가짐, 즉 shallowEqual
compare: shallowEqual
};
이렇게 해서, 함수형 컴포넌트에 shouldComponentUpdate 를 붙였습니다.接下来的 일은 추측할 수 있을 것입니다:
// ref: react-16.6.3/react/packages/react-reconciler/src/ReactFiberBeginWork.js
function updateMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
updateExpirationTime,
renderExpirationTime: ExpirationTime,
): null | Fiber {
// Default to shallow comparison
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
}
따라서, 구현으로 보면, React.memo() 라는 API 는 memo 와 별로 관계가 없고, 실제 의미는: 함수형 컴포넌트에도 "shouldComponentUpdate" 라이프사이클이 생겼다는 것입니다
주의, compare 의 기본은 shallowEqual 이므로, React.memo 의 두 번째 파라미터 compare 의 실제 의미는shouldNotComponentUpdate로, 우리가 알고 있는 반대的那个이 아닙니다. API 설계는 확실히 조금 혼란을招き, 일부러 반대의 것을 도입했습니다:
Unlike the shouldComponentUpdate() method on class components, this is the inverse from shouldComponentUpdate.
P.S.RFC 定稿過程中 두 번째 파라미터는 확실히 논의를 불렀습니다 (equal, arePropsEqual, arePropsDifferent, renderOnDifference, isEqual, shouldUpdate... 등 10000 개 이내), 상세한 내용은 React.memo() 참조
수동으로 memo 를 구현?
话说回来, 이러한 고계 컴포넌트는実は 구현이 어렵지 않습니다:
function memo(render, shouldNotComponentUpdate = shallowEqual) {
let oldProps, rendered;
return function(newProps) {
if (!shouldNotComponentUpdate(oldProps, newProps)) {
rendered = render(newProps);
oldProps = newProps;
}
return rendered;
};
}
수동으로 구현한 이 盗版은 공식 버전과 기능상 동등 (심지어 성능도 뒤지지 않음) 이므로,又一个 錦上添花의 것입니다
三.React.lazy: Code-Splitting with Suspense
매우 아름다운 특성으로, 篇幅의 제한으로, 상세한 내용은 React Suspense 참조
四.static contextType
v16.3 은 新 Context API 를 릴리스했습니다:
const ThemeContext = React.createContext('light');
class ThemeProvider extends React.Component {
state = {theme: 'light'};
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
class ThemedButton extends React.Component {
render() {
return (
// 이 부분은 매우 귀찮아 보임, context 를 읽을 ��인데
<ThemeContext.Consumer>
{theme => <Button theme={theme} />}
</ThemeContext.Consumer>
);
}
}
class 컴포넌트가 Context 데이터에 액세스하기 쉽게 하기 위해, static contextType 특성이 추가되었습니다:
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
let theme = this.context;
return (
// 소란이 멈췄다
<Button theme={theme} />
);
}
}
그 중에서 contextType(주의, 이전의 旧는 복수 s 가 있고, contextTypes 라고 불립니다) 는 React.createContext() 반환 타입만 지원하며, 旧 Context API 의 this.context 를翻新했습니다 (단일 값이 되었습니다, 이전은 오브젝트)
用法은 그다지 변태적이지 않지만, 단일 Context 값의 액세스만 지원합니다.一堆의 Context 값에 액세스하고 싶다면, 위의 매우 귀찮아 보이는那种方式 을 쓸 수밖에 없습니다:
// A component may consume multiple contexts
function Content() {
return (
// 。。。。
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
五.static getDerivedStateFromError()
static getDerivedStateFromError(error)
又一个 에러 처리 API:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
用法은 v16.0 의 componentDidCatch(error, info) 와 매우 닮았습니다:
class ErrorBoundary extends React.Component {
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
}
二者는 모두 자식 트리 렌더링 에러 후에 트리거되지만, 트리거 시기에 미묘한 차이가 있습니다:
-
static getDerivedStateFromError: render 단계 에서 트리거되며, 부작용을 포함하는 것을 허용하지 않음 (否则 여러 번 실행으로 문제가 발생) -
componentDidCatch: commit 단계 에서 트리거되므로, 부작용을 포함하는 것을 허용함 (logErrorToMyService등)
전자의 트리거 시기는 충분히 빠르므로, 더 많은 구제 조치를 취할 수 있습니다. 예를 들어 null ref 가 연쇄 에러를 일으키는 것을 회피
또 하나의 차이는 Did 시리즈 라이프사이클 (componentDidCatch 등) 은 SSR 을 지원하지 않고, getDerivedStateFromError 는 설계상 SSR 을 고려했습니다 (현재 v16.6.3 은 아직 지원하지 않지만, 지원한다고 했습니다)
현재 이 두 API 는 기능상 중복되어 있으며, 모두 자식 트리 에러 후에 state 를 변경하여 UI 다운그레이드를 할 수 있지만, 후속은 각각의 역할을 세분화합니다:
-
static getDerivedStateFromError: 전담 UI 다운그레이드 -
componentDidCatch: 전담 에러 보고
六.시대遅れ API
又一个 2 개의 API 가 冷宮에打入됩니다:
-
ReactDOM.findDOMNode(): 성능 원인 및 설계상의 문제, ref forwarding 로의乗り換え를 권장 -
旧 Context API: 성능 및 구현方面的 이유, 新 Context API 로의乗り換え를 권장
P.S. 暂时 아직 사용 가능하지만, 장래 버전에서 삭제됩니다, StrictMode 를借助하여 이행을 완료할 수 있습니다
七.まとめ
함수형 컴포넌트에도 "shouldComponentUpdate" 가 도래, 그리고 아름다운 Code-Splitting 지원, 및 Context Consumer 의 번잡한 통증의 완화 패치 API, 그리고 역할이 명확한 UI 층의 兜底方案
13 종류의 React 컴포넌트
v16.6 은 몇 종류의 컴포넌트를 추가했습니다 (REACT_MEMO_TYPE, REACT_LAZY_TYPE, REACT_SUSPENSE_TYPE), 세어보면, 竟然 이렇게 많아졌습니다:
-
REACT_ELEMENT_TYPE: 일반 React 컴포넌트 타입, 예를 들어<MyComponent /> -
REACT_PORTAL_TYPE: Protals 컴포넌트,ReactDOM.createPortal() -
REACT_FRAGMENT_TYPE: Fragment 가상 컴포넌트,<></>또는<React.Fragment></React.Fragment>또는[、] -
REACT_STRICT_MODE_TYPE: 時代遅れ API 검사付き의 엄격 모드 컴포넌트,<React.StrictMode> -
REACT_PROFILER_TYPE: 컴포넌트 범위 성능 분석을开启하는 데 사용, Profiler RFC 참조, 현재는 아직 실험적 API,<React.unstable_Profiler>안정 후에는<React.Profiler>로 변화 -
REACT_PROVIDER_TYPE: Context 데이터의 생산자 Context.Provider,<React.createContext(defaultValue).Provider> -
REACT_CONTEXT_TYPE: Context 데이터의 소비자 Context.Consumer,<React.createContext(defaultValue).Consumer> -
REACT_ASYNC_MODE_TYPE: 비동기 특성을开启하는 비동기 모드 컴포넌트, 時代遅れ,REACT_CONCURRENT_MODE_TYPE로乗り換え -
REACT_CONCURRENT_MODE_TYPE: 비동기 특성을开启하는 데 사용, 暂时 아직 방출되지 않았으며, Demo 단계 에 있음,<React.unstable_ConcurrentMode>안정 후에는<React.ConcurrentMode>로 변화 -
REACT_FORWARD_REF_TYPE: 아래로 Ref 를传递하는 컴포넌트,React.forwardRef() -
REACT_SUSPENSE_TYPE: 컴포넌트 범위 지연 렌더링,<Suspense fallback={<MyLoadingComponent>}> -
REACT_MEMO_TYPE: PureComponent 와 유사한 고계 컴포넌트,React.memo() -
REACT_LAZY_TYPE: 동적으로 도입하는 컴포넌트,React.lazy()
曾几何时, v15-只有 1 종류의 REACT_ELEMENT_TYPE 였습니다……
아직 댓글이 없습니다