방법: Fluid Framework에서 대상 그룹 기능 사용
이 자습서에서는 React와 함께 Fluid Framework 대상 그룹을 사용하여 컨테이너에 연결하는 사용자의 시각적 데모를 만드는 방법에 대해 알아봅니다. 대상 그룹 개체는 컨테이너에 연결된 모든 사용자와 관련된 정보를 보유합니다. 이 예에서는 Azure 클라이언트 라이브러리를 사용하여 컨테이너 및 대상 그룹을 만듭니다.
다음 이미지는 ID 단추와 컨테이너 ID 입력 필드를 보여 줍니다. 컨테이너 ID 필드를 비워두고 사용자 ID 단추를 클릭하면 새 컨테이너가 만들어지고 선택된 사용자로 조인됩니다. 또는 최종 사용자는 컨테이너 ID를 입력하고 사용자 ID를 선택하여 기존 컨테이너에 선택한 사용자로 조인할 수 있습니다.
다음 이미지는 상자로 표시된 컨테이너에 연결된 여러 사용자를 보여 줍니다. 파란색 윤곽선의 상자는 클라이언트를 보고 있는 사용자를 나타내고 검은색 윤곽선의 상자는 연결된 다른 사용자를 나타냅니다. 새로운 사용자가 고유 ID로 컨테이너에 연결되면 상자 수가 증가합니다.
참고 항목
이 자습서에서는 사용자가 Fluid Framework 개요에 익숙하고 QuickStart를 완료했다고 가정합니다. 또한 React, React 프로젝트 만들기 및 React Hooks의 기본 사항에 익숙해야 합니다.
프로젝트를 만듭니다.
명령 프롬프트를 열고 프로젝트를 만들 부모 폴더로 이동합니다. 예:
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 Service에 대한 연결을 만드는 데 필요한 InsecureTokenProvider를 정의합니다. 다음 명령을 실행하여 라이브러리를 설치합니다.
npm install @fluidframework/azure-client @fluidframework/test-client-utils fluid-framework
프로젝트 코딩
상태 변수 및 구성 요소 보기 설정
코드 편집기에서
\src\App.js
파일을 엽니다. 모든 기본import
문을 삭제합니다. 그런 다음return
문에서 모든 태그를 삭제합니다. 그런 다음 구성 요소 및 React 후크에 대한 가져오기 문을 추가합니다. 이후 단계에서 가져온 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
값은 컨테이너에 연결할 사용자 ID이고containerId
값은 로드할 컨테이너입니다.handleSelectUser
및handleContainerNotFound
함수는 두 보기에 대한 콜백으로 전달되고 상태 전환을 관리합니다.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
메서드는 컨테이너 및 대상 그룹 서비스 만들기를 처리합니다.
컨테이너 및 대상 그룹 가져오기
도우미 함수를 사용하여 Audience 개체에서 보기 계층(React 상태)으로 Fluid 데이터를 가져올 수 있습니다. tryGetAudienceObject
메서드는 사용자 ID가 선택된 후 보기 구성 요소가 로드될 때 호출됩니다. 반환된 값은 React 상태 속성에 할당됩니다.
다음 코드로
TODO 1
을 교체합니다. 값은userId
userName
containerId
앱 구성 요소에서 전달됩니다. 없는containerId
경우 새 컨테이너가 만들어집니다. 또한containerId
는 URL 해시에 저장됩니다. 새 브라우저에서 세션에 진입하는 사용자는 기존 세션 브라우저에서 URL을 복사하거나localhost:3000
으로 이동하여 컨테이너 ID를 수동으로 입력할 수 있습니다. 이 구현에서는 사용자가 존재하지 않는 컨테이너 ID를 입력하는 경우 try catch에서 호출을 래핑getContainer
하려고 합니다. 자세한 내용은 컨테이너 설명서를 참조하세요.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 대상 그룹을 가져오는 방법을 정의했으므로 이제 대상 그룹 디스플레이 구성 요소가 탑재될 때 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}/> )
참고 항목
연결 전환으로 인해
getMyself
가undefined
를 반환하는 짧은 타이밍 창이 발생할 수 있습니다. 이는 현재 클라이언트 연결이 아직 대상 그룹에 추가되지 않았기 때문에 일치하는 연결 ID를 찾을 수 없기 때문입니다. React가 대상 그룹 멤버가 없는 페이지를 렌더링하는 것을 방지하기 위해membersChanged
에서updateMembers
를 호출하는 수신기를 추가합니다. 이는 컨테이너가 연결될 때 서비스 대상 그룹이membersChanged
이벤트를 방출하기 때문에 작동합니다.
보기 만들기
다음 코드로
TODO 5
을 교체합니다. AudienceDisplay 구성 요소에서 전달된 각 멤버에 대해 목록 구성 요소를 렌더링하고 있습니다. 각 멤버에 대해 먼저member.userId
를currentMember.userId
와 비교하여 해당 멤버가isSelf
인지 확인합니다. 이렇게 하면 클라이언트 사용자를 다른 사용자와 구분하고 구성 요소를 다른 색상으로 표시할 수 있습니다. 그런 다음 목록 구성 요소를list
배열로 푸시합니다. 각 구성 요소에는 멤버 데이터(예: 및additionalDetails
.)가userId
userName
표시됩니다.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
함수는 부모 앱 구성 요소의 상태 변수를 업데이트하고 보기 변경 메시지를 표시합니다.handleSubmit
메서드는TODO 2
에서 구현될 단추 요소에 의해 트리거됩니다. 또한handleChange
메서드는containerId
상태 변수를 업데이트하는 데 사용됩니다. 이 메서드는TODO 2
에 구현된 입력 요소 이벤트 수신기에서 호출됩니다. 또한 ID가containerIdInput
(TODO 2
에 정의됨)인 HTML 요소에서 값을 가져와서containerId
를 업데이트합니다.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 서버 시작 및 애플리케이션 실행
참고 항목
이 방법의 나머지 부분과 일치시키기 위해 이 섹션에서는 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
으로 이동합니다. 이때 첫 번째 브라우저 탭의 URL 진행 http://localhost:3000/#
에서 확인할 수 있는 컨테이너 ID 값을 입력합니다.
참고 항목
이 데모가 Webpack 5와 호환되도록 하려면 추가 종속성을 설치해야 할 수도 있습니다. "buffer" 또는 "url" 패키지와 관련된 컴파일 오류가 발생하면 npm install -D buffer url
을 실행하고 다시 시도하세요. 이 문제는 Fluid Framework의 향후 릴리스에서 해결될 것입니다.
다음 단계
userConfig
의additionalDetails
필드에서 더 많은 키/값 쌍으로 데모를 확장해 보세요.- SharedMap 또는 SharedString과 같은 분산 데이터 구조를 활용하는 협업 애플리케이션에 대상 그룹을 통합하는 것을 고려합니다.
- 대상 그룹에 대해 자세히 알아봅니다.
팁
코드를 변경하면 프로젝트가 자동으로 다시 빌드되고 애플리케이션 서버가 다시 로드됩니다. 그러나 컨테이너 스키마를 변경하면 애플리케이션 서버를 닫고 다시 시작해야만 적용됩니다. 이렇게 하려면 명령 프롬프트에 포커스를 두고 Ctrl-C를 두 번 누릅니다. 그런 다음 다시 npm run start
를 실행합니다.