コンポーネントの表示更新直前に実行されるbeforeUpdate関数
beforeUpdateは、コンポーネントの表示内容が「更新される直前」に実行されるライフサイクル関数です。
利用例を図3のサンプルで説明します。ラジオボタンでスマートフォンのメーカーを選択すると画面に表示されますが、「Blackberry」を選択した時だけアラート表示するとともに、Blackberryは選択されずに元の選択内容に戻ります。
図3の画面表示(HTML)は、リスト3の通り記述されています。ラジオボタンの<input>要素にbind:group属性を記述することで、選択値がvalue変数に紐づけられています。value変数の内容は(1)で画面に表示されます。
<div>どのメーカーのスマホを使いますか?</div> <label> <input type="radio" bind:group={value} value="Apple" />Apple </label> <label> <input type="radio" bind:group={value} value="Google" />Google </label> <label> <input type="radio" bind:group={value} value="Blackberry" />Blackberry </label> <label> <input type="radio" bind:group={value} value="motorola" />motorola </label> <div> あなたが選んだのは:<span id="selectedValue">{value}</span> <!--(1)--> </div>
一方、<script>部はリスト4の通りです。
<script lang="ts"> import { beforeUpdate } from 'svelte'; // 選択された値を格納する変数 let value = 'Apple'; // コンポーネントの表示更新直前に実行される処理 ...(1) beforeUpdate(() => { // 変更前の選択値を取得 ...(1a) const beforeSelectedValue = ( document.getElementById('selectedValue') as HTMLSpanElement ).innerText; // 変更後の選択値で条件分岐 ...(1b) if (value === 'Blackberry') { alert(`${value}はもうスマホを売っていないのです...`); // 変更前の選択値に戻す ...(1c) value = beforeSelectedValue; } }); </script>
beforeUpdate関数は(1)で利用しています。(1a)ではリスト3(1)で画面に表示された選択値の内容をbeforeSelectedValue変数に格納しています。beforeUpdate関数は画面が更新される直前に実行されるため、beforeSelectedValue変数には変更前の選択値が格納されます。
一方、ラジオボタンと紐づけられたvalue変数は、この時点で変更後の選択値に更新されているため、(1b)でvalue変数の値(変更後の選択値)が「Blackberry」の場合だけ、アラートでメッセージを表示後、(1c)でvalueにbeforeSelectedValue変数の値(変更前の選択値)を代入します。この処理によって、Blackberryの選択を取り消して、直前の選択状態に戻るようになります。
コンポーネントの表示更新直後に実行されるafterUpdate関数
afterUpdateは、コンポーネントの表示内容が「更新された直後」に実行されるライフサイクル関数です。
利用例を図4のサンプルで説明します。スマートフォンの一覧とともに、画面下部にテキストボックスと「追加」ボタンが表示されます。テキストボックスに文字列を入力して「追加」ボタンをクリックすると、一覧の一番下に入力内容が追加されるとともに、テキストボックスが常に表示されるように画面がスクロールしていきます。
図4のサンプルの実装内容を説明します。まず、一覧に表示する内容は、<script>部のphoneList配列に、リスト5の通り定義します。また、テキストボックスの入力内容を格納するinputPhone変数も同時に定義します。
// 画面に一覧表示する内容 let phoneList: Array<string> = [ 'Apple iPhone 16', 'motorola edge 50 pro', 'SHARP AQUOS wish4', 'OPPO Reno11 A' ]; // テキストボックスに入力された文字列 let inputPhone: string = '';
画面表示(HTML)はリスト6の通りです。(1)のリスト部では{#each}~{/each}記述により、phoneList配列の内容を一覧表示します。(2)の入力部ではテキストボックスの<input>要素に記述されたbind:value属性により、テキストボックスの入力内容をinputPhone変数に紐づけています。
<!-- リスト部 ...(1)--> <div id="listArea"> {#each phoneList as phone} <div class="phone">{phone}</div> {/each} </div> <!-- 入力部 ...(2)--> <div id="inputArea"> <input type="text" bind:value={inputPhone} /> <button on:click={addToList}>追加</button> </div>
リスト6(2)の追加ボタンクリック時に実行されるaddToList関数の内容はリスト7の通りです。
const addToList = () => { // 入力された文字列をphoneListに追加 ...(1) phoneList = [...phoneList, inputPhone]; // テキストボックスの文字列を初期化 ...(2) inputPhone = ''; // スクロール位置設定フラグを設定する ...(3) isSetScrollPosition = true; };
(1)で、テキストボックスに入力された文字列(inputPhone変数)を追加して、phoneList配列を更新しています。(2)でテキストボックスの文字列を初期化後、(3)ではスクロール位置設定フラグ(isSetScrollPosition変数)をtrueにしています。このフラグはafterUpdate関数の処理で参照します。
[補足]画面に反映されるように配列を更新する方法
リスト7(1)では、スプレッド構文「...phoneList」でphoneList配列の各要素を取得し、inputPhoneと合わせて新しい配列を生成してphoneListに代入しています。これは、Svelteのリアクティブ機能が代入をきっかけに実行されるためです。詳細はSvelteの公式チュートリアルも参照してください。
ここで配列への要素追加を「phoneList.push(inputPhone)」で実行すると、リアクティブ機能が実行されず、要素追加が画面に反映されません。
ここでの本題となるafterUpdate関数の実装は、リスト8の通りです。
afterUpdate(() => { // スクロール位置設定フラグが立っている場合のみ処理 ...(1) if (isSetScrollPosition) { // リスト部と入力部の高さを求める ...(2) const listArea = document.getElementById('listArea') as HTMLDivElement; const inputArea = document.getElementById('inputArea') as HTMLDivElement; const listAreaHeight = listArea.offsetHeight; const inputAreaHeight = inputArea.offsetHeight; // スクロール位置を設定 ...(3) window.scrollTo(0, listAreaHeight + inputAreaHeight - window.innerHeight); } // スクロール位置設定フラグを戻す ...(4) isSetScrollPosition = false; });
afterUpdate関数は、コンポーネントのあらゆる表示更新時(例えばこのサンプルでは、テキストボックスの内容が変更されるたび)に実行されます。そのため(1)の条件分岐で、isSetScrollPosition変数がtrueの場合のみスクロール位置調整の処理が行われるようにしています。
スクロール位置調整の処理では、まず(2)で、リスト部と入力部の高さを求めています。afterUpdate関数は画面更新直後に実行されるため、ここで取得される高さは、画面更新後のものであることに注意してください。取得した高さを利用して、(3)でスクロール位置を設定しています。リスト部と入力部の高さから、表示領域自体の高さ(window.innerHeight)を減算したものがスクロール位置となります。一連の処理後、(4)でスクロール位置設定フラグを戻しています。
[補足]body要素のマージンを0に設定
図4のサンプルでは、マージンによるスクロール位置の誤差を防ぐため、ページ自体のHTMLファイル(app.html)に、body要素のマージンを0にする記述を行っています。
<style>body { margin: 0; }</style>