方法: Fluid Framework で視聴者機能を使用する
このチュートリアルでは、Fluid Framework の対象ユーザー を React とともに使用して、コンテナーに接続するユーザーの視覚的なデモを作成する方法について説明します。 対象ユーザー オブジェクトには、コンテナーに接続されているすべてのユーザーに関連する情報が保持されます。 この例では、Azure クライアント ライブラリを使用して、コンテナーと対象ユーザーを作成します。
次の画像は、ID ボタンとコンテナー ID 入力フィールドを示しています。 コンテナー ID フィールドを空白のままにし、ユーザー ID ボタンをクリックすると、新しいコンテナーが作成され、選択したユーザーとして参加します。 または、エンドユーザーがコンテナー ID を入力し、ユーザー ID を選択して、選択したユーザーとして既存のコンテナーに参加することもできます。
次の画像は、コンテナーに接続されている複数のユーザーをボックスごとに表しています。 青で囲んだボックスは、クライアントを表示しているユーザーを表し、黒で囲んだボックスは接続されている他のユーザーを表します。 新しいユーザーが一意の ID でコンテナーにアタッチすると、ボックスの数が増えます。
Note
このチュートリアルは、Fluid Framework の概要に精通していること、およびクイックスタートを完了していることを前提としています。 また、React、React プロジェクトの作成、React のフックの基本についても理解している必要があります。
プロジェクトを作成する
コマンド プロンプトを開き、プロジェクトを作成する親フォルダー (例:
C:\My Fluid Projects
) に移動します。プロンプトで次のコマンドを実行します。 (CLI が npm ではなく npx であることに注目してください。これは、Node.js をインストールしたときにインストールされています。)
npx create-react-app fluid-audience-tutorial
プロジェクトは、
fluid-audience-tutorial
という名前のサブフォルダーに作成されます。 コマンドcd fluid-audience-tutorial
を使用して、それに移動します。プロジェクトでは、次の Fluid ライブラリが使用されます。
ライブラリ 説明 fluid-framework
クライアント間でデータを同期する SharedMap 分散データ構造 が含まれます。 @fluidframework/azure-client
Fluid サービス サーバーへの接続を定義し、Fluid コンテナーの開始スキーマが定義されます。 @fluidframework/test-client-utils
Fluid サービスへの接続を作成するために必要な InsecureTokenProvider が定義されます。 次のコマンドを実行して、ライブラリをインストールします。
npm install @fluidframework/azure-client @fluidframework/test-client-utils fluid-framework
プロジェクトをコーディングする
状態ライブラリとコンポーネント ビューを設定する
コード エディターで、ファイル
\src\App.js
を開きます。 すべての既定のimport
ステートメントを削除します。 次に、return
ステートメントからすべてのマークアップを削除します。 次に、コンポーネントと React のフックの import ステートメントを追加します。 インポートした AudienceDisplay および UserIdSelection コンポーネントを後の手順で実装します。 ファイルは次のようになります。import { useState, useCallback } from "react"; import { AudienceDisplay } from "./AudienceDisplay"; import { UserIdSelection } from "./UserIdSelection"; export const App = () => { // TODO 1: Define state variables to handle view changes and user input return ( // TODO 2: Return view components ); }
TODO 1
を次のコードに置き換えます。 このコードにより、アプリケーション内で使用されるローカル状態変数が初期化されます。displayAudience
の値は、AudienceDisplay コンポーネントと UserIdSelection コンポーネントのどちらをレンダリングするかを決定します (TODO 2
を参照)。userId
の値はコンテナーに接続するときに使用するユーザー識別子であり、containerId
の値は読み込むコンテナーです。handleSelectUser
とhandleContainerNotFound
の関数は、2 つのビューへのコールバックとして渡され、状態遷移を管理します。handleSelectUser
は、コンテナーの作成または読み込みを試みたときに呼び出されます。handleContainerNotFound
は、コンテナーの作成または読み込みに失敗したときに呼び出されます。userId と containerId の値は、
handleSelectUser
関数を介して UserIdSelection コンポーネントから取得されます。const [displayAudience, setDisplayAudience] = useState(false); const [userId, setUserId] = useState(); const [containerId, setContainerId] = useState(); const handleSelectUser = useCallback((userId, containerId) => { setDisplayAudience(true) setUserId(userId); setContainerId(containerId); }, [displayAudience, userId, containerId]); const handleContainerNotFound = useCallback(() => { setDisplayAudience(false) }, [setDisplayAudience]);
TODO 2
を次のコードに置き換えます。 前述のように、displayAudience
変数は AudienceDisplay コンポーネントと UserIdSelection コンポーネントのどちらをレンダリングするかを決定します。 状態変数を更新する関数も、プロパティとしてコンポーネントに渡されます。(displayAudience) ? <AudienceDisplay userId={userId} containerId={containerId} onContainerNotFound={handleContainerNotFound}/> : <UserIdSelection onSelectUser={handleSelectUser}/>
AudienceDisplay コンポーネントを設定する
コード エディターでファイル
\src\AudienceDisplay.js
を作成して開きます。 次のimport
ステートメントを追加します。import { useEffect, useState } from "react"; import { SharedMap } from "fluid-framework"; import { AzureClient } from "@fluidframework/azure-client"; import { InsecureTokenProvider } from "@fluidframework/test-client-utils";
ユーザーとコンテナーを定義するには、Fluid Framework ライブラリからインポートされたオブジェクトが必要です。 以後のステップでは、AzureClient と InsecureTokenProvider を使用してクライアント サービスが構成され (
TODO 1
を参照)、SharedMap を使用してコンテナーを作成するために必要なcontainerSchema
が構成されます (TODO 2
を参照)。次の関数コンポーネントとヘルパー関数を追加します。
const tryGetAudienceObject = async (userId, userName, containerId) => { // TODO 1: Create container and return audience object } export const AudienceDisplay = (props) => { //TODO 2: Configure user ID, user name, and state variables //TODO 3: Set state variables and set event listener on component mount //TODO 4: Return list view } const AudienceList = (data) => { //TODO 5: Append view elements to list array for each member //TODO 6: Return list of member elements }
AudienceDisplay と AudienceList は、対象ユーザー データの取得とレンダリングを処理する機能コンポーネントであり、
tryGetAudienceObject
メソッドは、コンテナーと対象ユーザーのサービスの作成を処理しています。
コンテナーと対象ユーザーの取得
ヘルパー関数を使用すると、対象ユーザー オブジェクトからビュー レイヤー (React state) に Fluid のデータを取得できます。 tryGetAudienceObject
メソッドは、ユーザー ID が選択された後、ビュー コンポーネントが読み込まれるときに呼び出されます。 戻り値は、React state プロパティに割り当てられます。
TODO 1
を次のコードに置き換えます。userId
userName
containerId
の値は、App コンポーネントから渡されることに注意してください。containerId
がない場合は、新しいコンテナーが作成されます。containerId
は URL ハッシュにも格納されます。 新しいブラウザーからセッションを入力するユーザーは、既存のセッション ブラウザーから URL をコピーするか、localhost:3000
に移動して、コンテナー ID を手動で入力できます。 この実装では、ユーザーが存在しないコンテナー ID を入力した場合に、getContainer
呼び出しを try catch でラップします。 詳細については、 Containers ドキュメントを参照してください。const userConfig = { id: userId, name: userName, additionalDetails: { email: userName.replace(/\s/g, "") + "@example.com", date: new Date().toLocaleDateString("en-US"), }, }; const serviceConfig = { connection: { type: "local", tokenProvider: new InsecureTokenProvider("", userConfig), endpoint: "http://localhost:7070", }, }; const client = new AzureClient(serviceConfig); const containerSchema = { initialObjects: { myMap: SharedMap }, }; let container; let services; if (!containerId) { ({ container, services } = await client.createContainer(containerSchema)); const id = await container.attach(); location.hash = id; } else { try { ({ container, services } = await client.getContainer(containerId, containerSchema)); } catch (e) { return; } } return services.audience;
コンポーネント マウントでの対象ユーザーの取得
Fluid の対象ユーザーの取得方法を定義したので、Audience Display コンポーネントがマウントされたときに tryGetAudienceObject
を呼び出すように React に指示する必要があります。
TODO 2
を次のコードに置き換えます。 ユーザー ID は、user1
user2
またはrandom
として親コンポーネントから取得されることに注意してください。 ID がrandom
の場合は、Math.random()
を使用して ID として乱数を生成します。 さらに、userNameList
で指定された ID に基づいて、名前がユーザーにマップされます。 最後に、接続されたメンバーだけでなく現在のユーザーも格納する状態変数を定義します。fluidMembers
はコンテナーに接続されているすべてのメンバーのリストを格納しますが、currentMember
はブラウザー コンテキストを表示している現在のユーザーを表すメンバー オブジェクトを含みます。const userId = props.userId == "random" ? Math.random() : props.userId; const userNameList = { "user1" : "User One", "user2" : "User Two", "random" : "Random User" }; const userName = userNameList[props.userId]; const [fluidMembers, setFluidMembers] = useState(); const [currentMember, setCurrentMember] = useState();
TODO 3
を次のコードに置き換えます。 これにより、コンポーネントがマウントされたときにtryGetAudienceObject
が呼び出され、返された対象ユーザーのメンバーがfluidMembers
とcurrentMember
に設定されます。 ユーザーが存在しない containerId を入力し、それらを UserIdSelection ビューに返す必要がある場合に、対象ユーザー オブジェクトが返されるかどうかを確認します (props.onContainerNotFound()
はビューの切り替えを処理します)。 また、audience.off
を返して、React コンポーネントのマウントを解除するときに、イベント ハンドラーを登録解除することをお勧めします。useEffect(() => { tryGetAudienceObject(userId, userName, props.containerId).then(audience => { if(!audience) { props.onContainerNotFound(); alert("error: container id not found."); return; } const updateMembers = () => { setFluidMembers(audience.getMembers()); setCurrentMember(audience.getMyself()); } updateMembers(); audience.on("membersChanged", updateMembers); return () => { audience.off("membersChanged", updateMembers) }; }); }, []);
TODO 4
を次のコードに置き換えます。fluidMembers
またはcurrentMember
が初期化されていない場合は、空白の画面がレンダリングされます。 AudienceList コンポーネントは、(次のセクションで実装される) スタイルを使用してメンバー データをレンダリングします。if (!fluidMembers || !currentMember) return (<div/>); return ( <AudienceList fluidMembers={fluidMembers} currentMember={currentMember}/> )
Note
接続の遷移により、
getMyself
がundefined
を返す、短いタイミング ウィンドウが発生する可能性があります。 これは、現在のクライアント接続がまだ対象ユーザーに追加されていなく、一致する接続 ID が見つからないためです。 React が対象ユーザーのメンバーがいないページをレンダリングしないようにするには、membersChanged
でupdateMembers
を呼び出すリスナーを追加します。 これが動作するのは、コンテナーが接続されたときにサービス対象ユーザーがmembersChanged
イベントを生成するためです。
ビューの作成
TODO 5
を次のコードに置き換えます。 AudienceDisplay コンポーネントから渡された各メンバーのリスト コンポーネントがレンダリングされています。 各メンバーについて、最初にmember.userId
とcurrentMember.userId
を比較して、そのメンバーがisSelf
かどうかを確認します。 これにより、クライアント ユーザーを他のユーザーと区別し、別の色でコンポーネントを表示できます。 次に、リスト コンポーネントをlist
配列にプッシュします。 各コンポーネントには、userId
userName
やadditionalDetails
などのメンバー データが表示されます。const currentMember = data.currentMember; const fluidMembers = data.fluidMembers; const list = []; fluidMembers.forEach((member, key) => { const isSelf = (member.userId === currentMember.userId); const outlineColor = isSelf ? 'blue' : 'black'; list.push( <div style={{ padding: '1rem', margin: '1rem', display: 'flex', outline: 'solid', flexDirection: 'column', maxWidth: '25%', outlineColor }} key={key}> <div style={{fontWeight: 'bold'}}>Name</div> <div> {member.userName} </div> <div style={{fontWeight: 'bold'}}>ID</div> <div> {member.userId} </div> <div style={{fontWeight: 'bold'}}>Connections</div> { member.connections.map((data, key) => { return (<div key={key}>{data.id}</div>); }) } <div style={{fontWeight: 'bold'}}>Additional Details</div> { JSON.stringify(member.additionalDetails, null, '\t') } </div> ); });
TODO 6
を次のコードに置き換えます。 これにより、list
配列にプッシュしたすべてのメンバー要素がレンダリングされます。return ( <div> {list} </div> );
UserIdSelection コンポーネントを設定する
コード エディターでファイル
\src\UserIdSelection.js
を作成して開きます。 このコンポーネントには、エンドユーザーがユーザー ID とコラボレーション セッションを選択できるようにする、ユーザー ID ボタンとコンテナー ID 入力フィールドが含まれます。 次のimport
ステートメントと機能コンポーネントを追加します。import { useState } from 'react'; export const UserIdSelection = (props) => { // TODO 1: Define styles and handle user inputs return ( // TODO 2: Return view components ); }
TODO 1
を次のコードに置き換えます。onSelectUser
関数は、親 App コンポーネントの状態変数を更新し、ビューの変更を求めるプロンプトを表示します。handleSubmit
メソッドは、TODO 2
で実装されるボタン要素によってトリガーされます。 また、handleChange
メソッドはcontainerId
状態変数を更新するためにも使用されます。 このメソッドは、TODO 2
で実装されている入力要素イベント リスナーから呼び出されます。 また、containerId
も IDcontainerIdInput
(TODO 2
で定義されています) で HTML 要素から値を取得するように更新します。const selectionStyle = { marginTop: '2rem', marginRight: '2rem', width: '150px', height: '30px', }; const [containerId, setContainerId] = (location.hash.substring(1)); const handleSubmit = (userId) => { props.onSelectUser(userId, containerId); } const handleChange = () => { setContainerId(document.getElementById("containerIdInput").value); };
TODO 2
を次のコードに置き換えます。 これにより、ユーザー ID ボタンとコンテナー ID 入力フィールドがレンダリングされます。<div style={{display: 'flex', flexDirection:'column'}}> <div style={{marginBottom: '2rem'}}> Enter Container Id: <input type="text" id="containerIdInput" value={containerId} onChange={() => handleChange()} style={{marginLeft: '2rem'}}></input> </div> { (containerId) ? (<div style={{}}>Select a User to join container ID: {containerId} as the user</div>) : (<div style={{}}>Select a User to create a new container and join as the selected user</div>) } <nav> <button type="submit" style={selectionStyle} onClick={() => handleSubmit("user1")}>User 1</button> <button type="submit" style={selectionStyle} onClick={() => handleSubmit("user2")}>User 2</button> <button type="submit" style={selectionStyle} onClick={() => handleSubmit("random")}>Random User</button> </nav> </div>
Fluid サーバーを起動してアプリケーションを実行する
Note
このハウツーの残りの部分と一致させるために、このセクションでは npx
と npm
のコマンドを使用して Fluid サーバーを起動します。 ただし、この記事のコードは、Azure Fluid Relay サーバーに対して実行することもできます。 詳細については、「方法: Azure Fluid Relay サービスをプロビジョニングする」および「方法: Azure Fluid Relay サービスに接続する」を参照してください
コマンド プロンプトで、次のコマンドを実行して Fluid サービスを開始します。
npx @fluidframework/azure-local-service@latest
新しいコマンド プロンプトを開き、プロジェクトのルート (例: C:/My Fluid Projects/fluid-audience-tutorial
) に移動します。 次のコマンドを使用して、アプリケーション サーバーを起動します。 ブラウザーでアプリケーションが開きます。 これには数分かかることがあります。
npm run start
ブラウザー タブで localhost:3000
に移動して、実行中のアプリケーションを表示します。 新しいコンテナーを作成するには、コンテナー ID の入力を空白にしたまま、ユーザー ID ボタンを選択します。 コンテナー セッションに参加する新しいユーザーをシミュレートするには、新しいブラウザー タブを開き、localhost:3000
に移動します。 今回は、最初のブラウザー タブの http://localhost:3000/#
に進む URL にあるコンテナー ID 値を入力します。
Note
このデモを Webpack 5 と互換性のあるものにするには、追加の依存関係をインストールする必要がある場合があります。 "buffer" または "url" パッケージに関連するコンパイル エラーが発生した場合は、npm install -D buffer url
を実行して、もう一度やり直してください。 これは、Fluid Framework の今後のリリースで解決される予定です。
次のステップ
userConfig
のadditionalDetails
フィールドでより多くのキーと値のペアを使用してデモを拡張してみてください。- SharedMap や SharedString などの分散データ構造を利用するコラボレーション アプリケーションに対象ユーザーを統合することを検討してください。
- 対象ユーザーに関する詳細情報を参照してください。
ヒント
コードを変更すると、プロジェクトが自動的に再構築され、アプリケーション サーバーが再読み込みされます。 ただし、コンテナー スキーマを変更すると、アプリケーション サーバーを閉じて再起動した場合にのみ有効になります。 これを行うには、コマンド プロンプトにフォーカスを設定し、Ctrl キーを押しながら C キーを 2 回押します。 その後、npm run start
をもう一度実行してください。