優先度の低い更新を後回しにできるTransition
Transitionは、優先度の低い画面更新を後回しにできる機能です。この機能を、図6のサンプルで説明します。このサンプルでは、テキストボックスに入力した文字を複数回、画面に表示します。また、特定のスマホ名を入力すると、会社名を前につけて表示します。
テキストボックスの文字を連続的に更新すると「更新中...…」が表示されて画面への反映が遅れ、その後まもなく反映されることがわかります。
図7 テキストボックスの入力が遅れて画面に反映される様子(p002-transition)
このサンプルで行っているTransitionの処理は、リスト5の通りです。
// state ...(1)
const [inputText, setInputText] = useState('');
const [dispText, setDispText] = useState('');
// useTransitionでisPendingとstartTransactionを取得 ...(2)
const [isPending, startTransition] = useTransition();
// テキストボックス文字列変更時の処理 ...(3)
function onChangeInput(e) {
// テキストボックスへの文字反映のため、setInputTextはすぐに行う ...(4)
setInputText(e.target.value);
// 優先度を低くする処理はstartTransitionで囲む ...(5)
startTransition(() => {
// 入力された文字列に対応した接頭辞を付与してdispTextに設定 ...(6)
const prefix = getPrefix(e.target.value);
setDispText(prefix + e.target.value);
});
}
(1)で、テキストボックスの文字列inputTextと、画面に表示する文字列dispTextの2つのstateを定義しています。(2)でuseTransitionフックを実行して、Transition中を表すisPending変数と、Transitionを開始するstartTransition関数を取得します。画面表示では、isPendingがtrueの時に「更新中...」を表示します(詳細はサンプルコード参照)。
(3)はテキストボックスの文字列が変更されたときの処理です。テキストボックスの文字列に対応するinputTextは、入力結果をすぐに反映するため、そのままsetInputTextメソッドを実行します(4)。
一方、入力された文字列に接頭辞をつけて表示するdispTextの処理(6)は、(5)の通りstartTransitionで囲んで、優先順位が低いものと指示します。この記述により、テキストボックスの文字列が短時間に多数変更されたときには、dispTextの表示更新が遅れる代わりに、inputTextの表示は遅れない(つまりテキストボックスへの入力が遅延しない)ようになります。
優先順位の低い状態変数を指定できるuseDeferredValue
startTransitionは、囲まれた処理に対して低優先度を指定しますが、処理の記述や処理中の判定(isPending)が必要ない場合は、状態変数そのものに対して低優先度を指定するuseDefferedValueフックが利用できます(リスト6)。
const [inputText, setInputText] = useState('');
// inputTextから、優先度が低いdispTextを生成 ...(1)
const dispText = useDeferredValue(inputText);
(1)の通りuseDeferredValueメソッドにinputTextを渡して生成したdispTextは、表示の優先順位が低いものと判定され、Transition同様、inputTextの表示(=テキストボックスへの文字入力)を邪魔しないよう、遅れて更新されます。実装の詳細はサンプルコード(p003-deferred)を参照してください。
サーバーサイドレンダリングの初期化処理の変更
これまで説明してきた例では、Webページはクライアント側の処理で表示されます。このような処理をクライアントサイドレンダリング(CSR)と呼びます。一方、Reactにはサーバー側でWebページのコンテンツを生成するサーバーサイドレンダリング(SSR)の機能も備えており、React 18で変更が加えられました。
図8のページをCSRとSSRで実装したサンプルで、React自身のCSRとSSRの機能を説明します。なお、現実的にはReact単体でSSRを実装することはほぼなく、Next.jsのような、Reactを利用するフレームワークを用いてSSRを実装することが多いですが、図8のサンプルではReact自身のSSR機能を説明するため、フレームワークに頼らずSSRを実装しています。
画面に表示するAppコンポーネントは、リスト7の通りです。(1)で、useStateフックでcount変数とsetCount関数を取得し、(2)でボタン押下時にcountを1増やす処理を記述しています。コンポーネントの表示内容は(3)です。
function App() {
// カウント ...(1)
const [count, setCount] = useState(0);
// ボタン押下時の処理(カウントを1増やす) ...(2)
function onClickButton() {
setCount(c => c + 1);
}
// コンポーネントのHTMLをJSXで記述して返却 ...(3)
return (
<div>
<h3>Reactカウントアップアプリ</h3>
<div>現在のカウント:{count}</div>
<button onClick={onClickButton}>カウントアップ</button>
</div>
);
}
export default App;
このAppコンポーネントをSSRで表示するReactの初期化処理はリスト8となります。
// React 17までの古い記述 ...(1)
// import ReactDOM from 'react-dom';
// ReactDOM.hydrate(<App/>, document.getElementById('root'));
// React 18からの記述 ...(2)
import ReactDOMClient from 'react-dom/client';
ReactDOMClient.hydrateRoot(document.getElementById('root'), <App />);
CSR(リスト1/2)同様、SSRでも、React 17までの記述(1)とReact 18からの記述(2)が存在します。hydrate(ハイドレート)とは、SSRで生成されたDOM構造(HTML記述)に、JavaScriptの処理を後から付与して、動的なWebページとして成立させる処理です。
SSRでは、表示するHTMLをサーバー側で生成する必要があります。p005-ssrサンプルでは、この処理をserver/index.jsにリスト9の通り実装しています。Node.js用Webサーバーライブラリー「Express」を利用しています。
const PORT = process.env.PORT | 3000;
const app = express();
// 「http://localhost:3000/」へアクセス時の処理 ...(1)
app.get('/', (req, res) => {
// HTML要素をJSXで記述 ...(2)
const htmlElem =
(略)
<div id='root'><App /></div>
(略)
// HTML文字列を生成して返却 ...(3)
const htmlString = ReactDOMServer.renderToString(htmlElem);
res.send(htmlString);
});
// buildフォルダー内のファイルにアクセスできるようにする ...(4)
app.use(express.static('./build'));
// Webサーバー開始 ...(5)
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
Webブラウザーからアクセスされたときの処理は(1)です。ここでは(2)で記述したHTML要素を(3)でHTML文字列にしてWebブラウザーに返却します。(4)は静的ファイルの配置指定、(5)はWebサーバーを開始する記述です。(2)のHTML要素に含まれた<App />、つまりAppコンポーネントがサーバーサイドレンダリングされて、Webブラウザーに返却されるHTMLに含まれるようになります。
WebブラウザーでWebページのソースを表示させると、CSRではWebブラウザー上で動的にAppコンポーネントを描画するため、Appコンポーネントがソースに含まれません。
一方SSRでは、HTMLのソース自体にAppコンポーネントの記述が含まれています。Webページの表示時には、この記述に対してハイドレートで動的な処理が付与されます。
サーバーサイドレンダリングとサーバーコンポーネント
SSRと似た用語として、Reactには「サーバーコンポーネント」があります。SSRではWebページのHTML自体をサーバー側で生成しますが、サーバーコンポーネントではコンポーネント単位の仮想DOMまでをサーバー側で生成してクライアント側に送り、それを元にクライアント側で描画します。対して、クライアント側で仮想DOMの生成から描画までをすべて行う(今までの)コンポーネントは「クライアントコンポーネント」です。
サーバーコンポーネントの詳細については、Next.jsのドキュメントも参照してください。
