一.概観
いくつかの便利な特性が追加されました:
-
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 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 はまだサポートしていませんが、サポートすると言っています)
現在これら 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_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 でした……
コメントはまだありません