フロントエンドエキスパートチームの@koba04 です。
10/25,26の2日間、ネバダで開催されたReact Conf 2018に参加して来ました。
今回は、気になっている人も多いKeynoteで発表されたHooksとConcurrent Reactについて紹介したいと思います。
今回紹介された内容は、2014年後半くらいからReactを見てきた中でも、最も大きな変更であると言えます。
React Conf 2018
React Conf 2018のストラップ
カンファレンスのトーク自体はすでにYouTubeで公開されているので、全トーク観ることが出来ます。
https://www.youtube.com/playlist?list=PLPxbbTqCLbGE5AihOSExAa4wUM-P42EIJ
Hooks Hooksは、GitHub上でも一切公開されておらず、React Conf 2018のタイミングで初めて公開された機能です。
これまでも、Stateful Function Componentという構想は語られていたので、それを実現するAPIと言えます。
トークの動画は下記です。
デモ主体でわかりやすいので、是非トークを観ることをオススメします。
React Today and Tomorrow - Sophie Alpert and Dan Abramov
また、こちらのトークでは実際にHooksを使ったデモが行われました。
90% Cleaner React - Ryan Florence
Hooksは、Function Componentに対する機能です。
現時点(2018/10/31)ではまだ提案段階の仕様ですが、16.7.0-alpha
のバージョンで試すことが可能です。
下記のRFCのIssueにはすでに500件以上のコメントがある通り、注目度と議論を招くAPIであることがわかります。
https://github.com/reactjs/rfcs/pull/68
まだRFCの段階にも関わらず、公式ドキュメントにも8つのセクションに渡ってHooksの解説が用意されています。
https://reactjs.org/docs/hooks-intro.html
したがって、今の段階でもこれを読んで16.7.0-alpha
を使うことで実際に試してみることが可能です。
まだ提案の段階にも関わらずここまで丁寧にドキュメントが用意されているのは、Hooksの意図を正しく伝えたいということを示していると思います。
重要なポイント 現時点では、提案段階の仕様であり実験的に試してフィードバックをもらう段階です。そのため、今後APIが変更される可能性があるため、現時点でプロダクション環境で利用することは推奨されていません
(Facebookではすでにプロダクションで使っているようですが)
Hooksを利用することで、これまでClass Componentでしか出来なかったことがFunction Componentでも可能になります。 ただし、Class Componentも現時点では引き続きサポートされるため、既存のClass ComponentをHooksを使って書き直す必要は少なくとも今の時点ではありません。
将来的には、Function Componentだけになることが予想されますが、それは近い将来の話ではありません。
Facebookでも50000以上のReact Componentがあり、それら全てをHooksを使ったものに書き直す予定はないと言っています。
Class ComponentとHooksを使ったFunction Componentは一緒に使うことが可能なので、
新しいコードではHooksを使うなど、段階的な導入が可能です。
Hooksとは HooksとはFunction Componentに対して追加された新しいAPIです。現状下記のHooksのAPIが提供されています。
Basic Hooks
useState
useEffect
useContext
Additional Hooks
useReducer
useCallback
useMemo
useRef
useImperativeMethods
useMutationEffect
useLayoutEffect
色々とありますが、まずはBasic Hooksとして定義されている3つのHooksだけでも知っておくといいと思います。
全てのHooksはuse
から始まります。
これは後述するCustom Hooksを作成する際の命名規則としても適用されます。
Hooksの使い方 それでは、それぞれのHooksについて簡単に使い方を示します。
useState
useState
は、ComponentのLocal Stateを利用するためのHookです。
下記のようにStateの初期値を渡すと、現在の値
とStateを更新するための関数
を配列で返します。
useState
を使うことで、Class Componentを使うことなくLocal Stateを作成できます。
import React, {useState} from 'react';
const Counter = props => {
const [count, setCount] = useState(0);
return (
<div>
<p>count is {count}</p>
<button onClick={() => setCount(count + 1)}>++</button>
<button onClick={() => setCount(count - 1)}>--</button>
</div>
);
} Stateを更新するための関数
は、オブジェクトを渡した場合にもsetState
のようにマージされません。置き換えられます。
useEffect
useEffect
は、副作用のある処理を定義するHookです。
API呼び出しやイベントの購読・解除など、componentDidMount
やcomponentDidUpdate
などのライフサイクルメソッドで行なっていたような処理を定義出来ます。
ただし、API呼び出しに対しては後述するSuspenseが適しているケースも多いです。
import React, {useEffect} from 'react';
const Header = props => {
// textが更新されるたびにdocument.titleを更新する
useEffect(() => {
document.title = props.text;
}, [props.text]);
return <header>{props.text}</header>;
} useEffect
は第一引数に副作用のある処理を定義します。
第一引数のみ定義すると、Function Componentの関数が呼ばれる度にuseEffect
に渡したコールバック関数も呼ばれます。
これは、componentDidMount
とcomponentDidUpdate
それぞれで呼び出す場合と同様と考えることができます。
useEffect
の第二引数には、配列を指定出来ます。
配列を渡した場合、配列のいずれかの要素が変更されていた場合のみ、第一引数のコールバック関数が呼ばれます。
つまり上記の場合、props.text
の値が変わった場合のみ、document.title
が更新されます。
これは、componentDidUpdate
でPropsの値をチェックして変更があった場合のみ処理を行なっていたケースで利用できます。
useEffect
に空の配列を渡すと、常に変化がないものとしてComponentのマウント時のみに、第一引数のコールバック関数が呼ばれます。
これは、componentDidMount
を利用していたようなケースに利用できます。
useEffect
は、関数を戻り値として返すことができます。
戻り値として返した関数はFunction Componentがアンマウントされる場合に呼び出されます。
これはサブスクリプションの登録、解除を行いたい場合に便利です。
下記は、イベントハンドラーの登録、解除をuseEffect
を使って行なう例です。
import React, {useEffect} from 'react';
const Resize = props => {
useEffect(() => {
const handler = () => {
// ...
};
window.addEventListener('resize', handler);
// イベントを解除する関数を返す
return () => window.removeEventListener(handler);
}, []);
return props.children;
} useEffect
はClass ComponentでのcomponentDidMount
とcomponentDidUpdate
とは違い、DOM更新処理後に非同期で呼ばれます。
そのため、componentDidMount
とcomponentDidUpdate
がDOM更新後に同期的に呼ばれることを保証したい場合には、後述のuseLayoutEffect
を使用します。
useContext
useContext
は文字通り、Contextを利用するためのHookです。
React.createContext
で作成されるオブジェクトを引数として渡します。
Consumer
のComponentを渡すわけではない点に注意してください。
import React, {useContext} from 'react';
const ThemeContext = React.createContext('normal');
const Button = props => {
const theme = useContext(ThemeContext);
return (
<button className={`${theme}-btn`} onClick={props.onClick}>
{props.text}
</button>
);
} useReducer
useReducer
は、reducer
とinitialState
を渡すと、state
とdispatch
関数を返すHookです。
Reduxをイメージするとわかりやすいと思います。
第三引数として、最初に発行するActionをオブジェクトとして渡すことも可能です。
import React, {useReducer} from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, 0);
return (
<div>
<p>count is {state}</p>
<button onClick={() => dispatch({type: 'INCREMENT'})}>++</button>
<button onClick={() => dispatch({type: 'DECREMENT'})}>--</button>
</div>
);
}; useReducer
で作成できるのはLocal Stateですが、Contextと組み合わせることで、下記のようにReduxのようなグローバルなStateを作ることも可能ではあります。
https://github.com/koba04/react-hooks-like-redux
余談ですが、Reduxに関してはすでにIssueで議論がある通り、react-redux
のconnectで行なっていたことをHooksのAPIとして提供することが予想されます。
https://github.com/reduxjs/react-redux/issues/1063
ちなみに、前述したuseState
はuseReducer
を使って実装されています。
https://github.com/facebook/react/blob/3db8b80e1501b161b213b0b5405590e4325a0414/packages/react-reconciler/src/ReactFiberHooks.js#L324-L332
useCallback
useCallback
は、少しわかりにくですが、メモ化された関数を返すHookです。
第二引数に配列として渡された値が変わった場合のみ、第一引数で渡したコールバック関数が再生成されます。
つまり、第二引数に配列として渡した値が変わらない限り、同じコールバックを取得できます。
これはPureComponentやReact.memoを使った場合など、子のComponentがPropsで渡された値を比較して最適化を行なっている場合に有効です。
import React, {useCallback} from 'react';
// React.memoを使った最適化
const Child = React.memo(props => (
<div>
<p>{props.name}({props.score})</p>
<button onClick={props.onClick}>++</button>
</div>
));
const Parent = () => {
const [score, setScore] = useState(0);
// scoreが変わった場合のみ再生成される
const onClick = useCallback(() => {
setScore(score + 1);
}, [score]);
// こう書くと毎回新しい関数が渡されてしまう
// const onClick = () => setScore(score + 1);
return <Child onClick={onClick} name="child" score={score} />;
}; useMemo
useMemo
は、useCallback
と少し似ていますが、こちらはメモ化されたコールバックではなく値 を返すHookです。
第二引数に配列として渡された値が変わった場合のみ、第一引数で渡したコールバック関数を再評価して値を返します。
例えば、Propsとして渡された巨大なリストをフィルタリングするような、計算に時間のかかるケースで使うことが想定されます。
import React, {useMemo} from 'react';
const ItemList = props => {
// itemsかtypeが変わった場合のみ再評価される
const filteredItems = useMemo(() => (
props.items.filter(item => item.type === props.type)
), [props.items, props.type])
return (
<ul>
{filteredItems.map(item => (
<Item key={item.id} item={item} />
))}
</ul>
);
}; useRef
useRef
は、その名前の通りRef
を格納するためのオブジェクトを作成することが主な用途のHookです。
ちなみにRef
以外のオブジェクトも格納できます。
import React, {useRef, useEffect} from 'react';
// マウント時にだけフォーカスをあてる
const Input = props => {
const el = useRef(null);
useEffect(() => {
el.current.focus();
}, [])
return <input type="text" ref={el} {...props} />;
} useImperativeMethods
useImperativeMethods
は、forwardRef
を使ってRef
経由で親からアクセスさせる際に、Ref
のオブジェクトをカスタマイズするためのHookです。
ユースケースとしては多くないと思います。
import React, {useImperativeMethods, useRef} from 'react';
const MyInput = forwardRef((props, ref) => {
const el = useRef(null);
useImperativeMethods(ref, () => ({
focus: () => {
el.current.focus();
}
}));
return <input type="text" ref={el} {...props} />;
});
// ここで取得できるrefはfocusメソッドのみ持ったオブジェクト
// <MyInput ref={ref} /> useMutationEffect
useMutationEffect
は、利用方法はuseEffect
と同じですが、実行されるタイミングが異なるHookです。
useMutationEffect
は、ReactがDOMを更新するのと同じタイミングで同期的に呼び出されます。
DOMが更新されるタイミングで処理をしたい場合に利用します。
DOM更新中に同期的に実行されるため、DOMのプロパティにアクセスすることで強制的なレイアウトの再計算が行われ、パフォーマンスに悪い影響を与える可能性があります。
基本的には利用することを避けるべきHookだと思います。
useLayoutEffect
useLayoutEffect
は、useMutationEffect
と似ていますが、こちらは全てのDOMの更新処理が終わった タイミングで同期的に呼ばれるHookです。
更新後のレイアウト情報をDOMから同期的に取得したい場合に利用します。
これは、Class ComponentのcomponentDidMount
とcomponentDidUpdate
が呼ばれるタイミングと同じタイミングで呼ばれます。
DOMの更新処理の後に同期的に呼ばれるため、可能であればuseEffect
を使う方が望ましいです。
useEffect
とuseMutationEffect
とuseLayoutEffect
のタイミングをUser Timingで示すと下記の通りです。
useMutationEffect
がHost Effects(DOMの更新処理のタイミング)に、useLayoutEffect
がライフサイクルメソッド呼び出しのタイミングに、useEffect
がそのあと非同期に呼ばれていることがわかります。
各Effectのタイミングの違い
Hooksの制限 HooksはFunction Componentの関数内から呼び出す必要があります。
また、HooksはFunction Component内において、毎回同じ順番で呼び出す必要があります。
そのため、関数内のトップレベルで呼び出すことが推奨されています。条件分岐などの中で呼び出すことは避ける必要があります。
const Foo = props => {
// OK
const [foo, setFoo] = useState('foo');
if (props.bar) {
// NG
const [bar, setBare] = useState('bar');
}
return null;
} これをチェックするためのeslint-plugin-react-hooks
というESLintのプラグインも同時に公開されています。
https://reactjs.org/docs/hooks-rules.html#eslint-plugin
この制限は、少しわかりにくく感じますが、HooksのAPIをシンプルにするためのトレードオフとして選択したとのことです。
Custom Hooksの作り方 Hooksは前述した通りFunction Componentから呼び出す必要がありますが、独自に定義したHooksの中で呼ぶことも可能です。
そのため、特定の処理をHooksとして共通化することが可能です。
Custom Hooksは前述したeslint-plugin-react-hooks
でのチェックを有効にするためにも、use
から始まる名前で作成することが推奨されています。
例えば、windowのサイズを返すHookは、下記のように作成して使用できます。
作成したHookは、Componentの描画には一切関与していないため、どこでも再利用できます。
import {useState, suseEffect} from 'react';
// windowサイズを返すHook
export const useWindowSize = () => {
const [width, setWidth] = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);
useEffect(() => {
const handler = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
}
window.addEventListener('resize', handler);
return () => window.addEventListener(handler);
}, []);
return [width, height];
} import React from 'react';
import {useWindowSize} from './useWindowSize';
const WindowSize = () => {
const [width, height] = useWindowSize();
return <p>width:{width}, height:{height}</p>
}; Hooksの目的 Componentのロジックを再利用するための手段としては、Higher Order Component(以下HOC)やRender Propsのパターンがありますが、
これらはコードを理解するのが難しかったり、"wrapper hell"と呼ばれる大量のComponentのネストが作成されるという問題があります。
recompose
のようなHOCのユーティリティライブラリを使っていて、気づかないうちに"wrapper hell"を作ってしまっているケースも多いと思います。
"wrapper hell"はデバッグの難しさや、見た目のViewの構造以上にComponentが大量にネストすることで、パフォーマンスに対しても影響があります。
また、サーバーからのデータ取得やイベントの購読・解除の処理をClass Componentが持つライフサイクルメソッドを使って記述しようとすると、コードが冗長になったりライフサイクル毎に処理が分断してしまう問題があります。
加えて、全てのライフサイクルメソッドの挙動を理解して適切にロジックを書くのは難しいという意見もあります。
Hooksは、Componentのロジックの再利用をするためのPrimitiveとして新しく提案されました。
例えばHooksでは、Componentのライフサイクルについて考える必要はなく、useEffect
などを使うことで「この値が変わったらこの処理をする」といったように、行いたい処理だけに注目してロジックを書くことができます。
Reactでは、Function Componentを使ったとしても内部ではFiberと呼ばれているデータ構造で状態を保持しています。
ライフサイクルメソッドがComponentに対するハイレベルなAPIであったのに比べて、HooksはFiberの内部構造に対するPrimitiveなAPIとして考えることもできます。
それは、@dan_abramov がKeynoteの中で、Atom(原子)に対するElectron(電子)のように、HooksはReactを構成する各機能の要素であると言っていることからもわかります。
React内部の仕組みについては、builderscon 2018で話したので、興味あれば見てみてください。
https://speakerdeck.com/koba04/algorithms-in-react
余談ですが、Hooksのソースを見てみると、HooksはUpdateと同様に単方向のLinked Listとして実装されているのがわかります。
https://github.com/facebook/react/blob/3db8b80e1501b161b213b0b5405590e4325a0414/packages/react-reconciler/src/ReactFiberHooks.js#L52-L60
これを、Function Componentが処理される度に初期化して順番にアクセスしていくため、必ず同じ順番に呼び出す必要があります。
他にも、useReducer
の実装を見てみると、更新処理は既存のsetStateの仕組みとは違い、別の更新キューを作って処理していることがわかったりと興味深い部分も多いです。
Keynoteでは、Hooksの導入の理由としてJavaScriptにおけるClassが、機械にとっても人間にとっても扱いが難しいものである点をあげています。
例えば、イベントハンドラー登録時のthis
の扱いにハマる人が多かったり、constructorなどの定型コードによりコード量が多くなったり。
また、Function Componentで書いた後に状態やライフサイクルメソッドが必要になった際に大きくコードを書き換える必要があるなど、ClassがDeveloper Experienceに与える影響を指摘しています。
また、機械にとってもClassは最適化(プロパティ名やメソッド名のminifyや処理のインライン化など)が難しいという問題点を指摘しています。
この辺りは、Prepackで取り組んでいる結果としての結論なのかなと思います。
他にもHot Reloadingも難しいという問題もあるようです。
現状、getSnapshotBeforeUpdate
やcomponentDidCatch
やgetDerivedStateFromError
など、一部のライフサイクルメソッドに対応するHooksが提供されていませんが、これらもいずれ提供される予定とのことです。
将来的にはHooksを使うことで、Class Componentを廃止する流れになるかと思います。
ですが、それは近い将来の話ではないので、慌てず少しずつ試していくのがいいかなと思います。
Concurrent React Hooksが話題になる中、次の日のKeynoteではConcurrent Reactについて発表されました。
トークの動画は下記です。
デモ主体でわかりやすいので、是非トークを観ることをオススメします。
Concurrent Rendering in React - Andrew Clark and Brian Vaughn
また、こちらのトークでは実際にSuspenseを使ったデモが行われました。
Moving To Suspense - Jared Palmer
Concurrent Reactは、これまでAsync Renderingと呼ばれていたもので、SuspenseとTime-slicingの2つの機能を指します。
今回は新しい何かが発表されたというより、現在の状況を改めて説明して、デモでどういったことが可能になるかを示すものでした。
Suspense Suspenseはレンダリングを中断(Suspend)できる機能です。
Suspenseは、Promise(正確にはthenableなオブジェクト)をPrimitiveとして扱うため、APIからのデータ取得やComponentの動的な読み込みだけでなく、Promiseでラップすることで様々な非同期処理に対して適用できます。
Suspenseの仕組みについては、過去に何度か紹介しているので興味あれば参照してください。
スライドで紹介しているバージョンからAPIの名前は変わっていますが、基本的な仕組みやコンセプトは同じです。
本トークでは、React.lazyと組み合わせた動的なComponent読み込みが紹介されていました。
import React from 'react';
const LazyContent = React.lazy(() => import('./LazyContent'));
const App = () => (
<main>
<section>
<p>Main</p>
<React.Suspense fallback={"loading..."}>
<LazyContent />
</React.Suspense>
</section>
</main>
); 上記では、LazyContent
のComponentを動的に読み込んで、読み込まれるまではloading...
のメッセージを表示しています。
上記の場合、loading...
のメッセージが即座に表示されますが、後述するReact.ConcurrentMode
を使うことで指定秒経過後にローディングをメッセージを出すといったことも可能になります。
上記で使用している動的なimportはまだ提案段階の仕様ですが、webpackなどを使っている場合にはすでに利用可能です。
このようにReact.lazy
を使うことで、webpackなどのツールのサポートは必要ですが、importの仕方を変えるだけで簡単に動的な読み込みが可能となります。
React.Suspense
のComponentは親の位置であればどこでも配置可能なため、ローディングで隠す範囲も簡単に指定できます。
また、例えば複数の非同期な依存関係がある場合に、それらの親にReact.Suspense
を定義することで、全ての非同期の依存が解決するまで、単一のローディングメッセージを出すといったことも可能です。
このようにSuspenseを使うことで、柔軟な非同期読み込みの制御が可能です。
APIリクエストについては、react-cache
というパッケージのunstable_createResource
を利用することが下記の通り記述できます。
ただし、react-cache
についてはまだStableではなく、キャッシュのInvalidationなど欠けている機能もあるのでまだ実際に利用できるレベルではないとしています。
import React from 'react';
import {unstable_createResource as createResource } from 'react-cache';
// Promiseを返す処理からリソースを作成する
const userResource = createResource(id => {
return fetch(`/api/user/${id}`).then(res => res.json())
});
const User = props => {
// リソースを読み込む
// リソースがキャッシュされていない場合は、Promiseがthrowされるのでレンダリングが止まる
const user = userResource.read(props.id);
return <div>{user.name}</div>;
}
const App = () => (
<React.Suspense fallback="loading...">
<User id={1} />
</React.Suspense>
); 上記では、指定したユーザのデータがキャッシュになければfetch
を使ってAPIから取得してキャッシュに格納します。
その際、React.Suspense
に指定しているloading...
のメッセージが表示されます。
その後、APIレスポンスを受け取るとレンダリングが再開されます。
再開時には、APIレスポンスのデータがキャッシュに格納されていてデータを同期的に取得できるので、User
が表示されます。
Suspenseの応用例として、"Moving To Suspense"のトークでは、React.Suspense
のfallback
に低解像度の画像を指定して、高解像度の画像を非同期にロードすることで、最初は低解像度の画像を表示してそのあと高解像度の画像に差し替えるといったデモも行われてました。
また、unstable_createResource
で作成したリソースはread
だけでなくpreload
というメソッドも持っていて、これを使うことで、事前にデータをキャッシュしておくことも可能です。
Suspenseについては、上記のような同期モードでの基本的な挙動については、すでにStableだとしていますが、react-cache
を使ったAPIデータの取得やReact.ConcurrentMode
を使ったConcurrentModeについては、まだまだStableでないとしています。
ConcurrentModeはレンダリングを非同期にするためのモードです。
React.ConcurrentMode
のComponentで囲むことで、その中はConcurrentModeになります。
また、ReactDOM.createRoot(domElement).render(ReactElement)
という新しい方法でDOMをマウントすることで、全体をConcurrentModeにすることも可能です。
部分的に非同期レンダリングを導入したい場合にはReact.ConcurrentMode
、アプリケーション全体を非同期レンダリングしたい場合にはReactDOM.createRoot
を使います。
ConcurrentModeでは、React.Suspense
にmaxDuration
を指定できます。
maxDuration
を指定することで、fallback
を表示するまでの時間を制御できます。
例えば、ネットワーク環境がよくてAPIリクエストが1秒以内に返ってくるような状況では、ローディングを表示せずにデータのロードを待って表示した方がスムーズです。
そういった場合に、maxDuration={1000}
のように指定することで、1秒経過してからローディングを表示するといった制御が可能です。
Suspenseを利用することで、非同期な依存関係の制御が簡単に柔軟にできるようになります。
また、サーバーサイドレンダリング対応についても取り組んでいるようです。
Time-slicing Time-slicingは、更新処理を優先度付け出来る機能です。
プライオリティをベースとした協調的マルチタスクにより、メインスレッドをブロックしない更新処理を可能にします。
これにはscheduler
というパッケージを利用します。
Reactでは、click
やinput
やtouchmove
など、ユーザーがすぐにリアクションを期待するようなイベントに対しては、InteractiveUpdateとして高い優先度が割り当てられます。
ただし、巨大なスプレッドシートに対するフィルタリングなど、一度の更新処理が重い場合にはユーザー入力や他の更新をブロックしてしまいます。
この場合、ユーザーにとってはフィルタリングするためのテキストボックスはすぐに反映されて欲しくて入力もブロックして欲しくないですが、フィルタリングした結果の表示については、少しくらい遅れても問題ないことが多いです。
このような場合に、Time-slicingを使うことで、フィルタリングするためのテキストボックスへの更新処理は優先度高く反映して、フィルタリングした結果については優先度を下げて反映を遅らせることが可能となります。
import React, {useState, useMemo} from 'react';
// このバージョンはまだnpmにpublishされていない
import {scheduleCallback} from 'scheduler';
const App = props => {
const [text, setText] = useState('');
const [filterText, setFilterText] = useState('');
const filteredItems = useMemo(() => (
props.items.filter(item => item.indexOf(filterText) !== -1)
), [filterText, props.items]);
return (
<main>
<Input
value={text}
onChange={value => {
setText(value);
// Filterする方の優先度は下げる
scheduleCallback(() => {
setFilterText(value);
})
}}
/>
<List items={filteredItems} />
</main>
);
}; 上記のサンプルは、使用しているscheduler
のscheduleCallback
がまだ公開されていないため動作しませんが、以前作成したデモがあるので、そちらを試してもらうと雰囲気が掴めるかなと思います。
また、ConcurrentModeでは、hidden
のPropsによるメインスレッドを邪魔しないプリレンダリングが可能です。
hidden
のPropsをDOM Componentに指定することで、その子要素はOffScreen Priorityという特殊な優先度で処理されます。
OffScreen Priorityはとても低い優先度として定義されているため、他の更新処理をブロックしません。
トークではタブUIの例が示されていましたが、ユーザーが表示するページを先に読み込んでおくことで高速なページ遷移を実現できます。
Suspenseと組み合わせることで、事前に非同期の依存関係をロードすることが可能になるため、さらに強力な仕組みとなります。
const Home = React.lazy(() => import('./Home'));
const User = React.lazy(() => import('./User'));
const Settings = React.lazy(() => import('./Settings'));
const App = props => (
<main>
<div hidden={props.page === "user"}>
<User />
</div>
<div hidden={props.page === "settings"}>
<Settings />
</div>
<div hidden={props.page !== "user" && props.page !== "settings"}>
<Home />
</div>
</main>
); 上記では、最初に表示したページ以外も動的に読み込んでプリレンダリングしています。
まとめ このように今回発表された内容は、Reactを使ったアプリケーションの作り方を変える大きなものでした。
ここからReduxなどの周辺ライブラリがこれらのAPIをどう使うかにもよりますが、時間をかけて今回紹介された内容を使った書き方に変わっていくと思います。
まだ安定版としてリリースされたという状況ではないので、盛り上がりに踊らされず少しずつ試していくのがよさそうです。
また、Keynoteでは、この他にも新しいProfilerについての紹介もあったのでそちらも注目です。
サイボウズのフロントエンドエキスパートチームでは、一緒にフロントエンド分野の問題解決に取り組んでくれる仲間を募集しています。