メインコンテンツへ移動

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 を 1 層包むだけで、普通の関数コンポーネントに 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 の 2 番目のパラメータ compare の実際の意味はshouldNotComponentUpdateで、私たちが知っている反対のものではありません。API 設計は確かに少し混乱を招き、わざわざ反対のものを導入しました:

Unlike the shouldComponentUpdate() method on class components, this is the inverse from shouldComponentUpdate.

P.S.RFC 定稿過程中 2 番目のパラメータは確かに議論を呼びました(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 getDerivedStateFromErrorrender 段階 でトリガーされ、副作用を含むことを許可しません(否则複数実行で問題が発生)

  • componentDidCatchcommit 段階 でトリガーされ、したがって副作用を含むことを許可します(logErrorToMyService など)

前者のトリガー時機は十分に早いため、より多くの救済措置を取ることができます。例えば null ref が連鎖エラーを引き起こすのを回避

もう一つの違いは Did シリーズライフサイクル(componentDidCatch など)は SSR をサポートせず、getDerivedStateFromError は設計上 SSR を考慮しています(現在 v16.6.3 はまだサポートしていませんが、サポートすると言っています)

現在これら 2 つの 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_TYPEREACT_LAZY_TYPEREACT_SUSPENSE_TYPE)、数えてみると、竟然こんなに多くなりました:

  • REACT_ELEMENT_TYPE:普通の React コンポーネントタイプ、例えば <MyComponent />

  • REACT_PORTAL_TYPEProtals コンポーネント、ReactDOM.createPortal()

  • REACT_FRAGMENT_TYPEFragment 仮想コンポーネント、<></> または <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_TYPEPureComponent に類似した高階コンポーネントReact.memo()

  • REACT_LAZY_TYPE動的に導入するコンポーネントReact.lazy()

曾几何时、v15-只有 1 種類の REACT_ELEMENT_TYPE でした……

参考資料

コメント

コメントはまだありません

コメントを書く