본문으로 건너뛰기

React 16.6 신 API

무료2018-11-17#JS#React代码拆分#React组件动态加载#React动态加载#React Lazy Component#React contextType vs contextTypes

함수형 컴포넌트에도 "shouldComponentUpdate" 가 도래, 그리고 아름다운 Code-Splitting 지원

一.개관

몇 가지 편리한 특성이 추가되었습니다:

  • 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 APIthis.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 였습니다……

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성