다음을 통해 공유


방법: Fluid Framework에서 대상 그룹 기능 사용

이 자습서에서는 React와 함께 Fluid Framework 대상 그룹을 사용하여 컨테이너에 연결하는 사용자의 시각적 데모를 만드는 방법에 대해 알아봅니다. 대상 그룹 개체는 컨테이너에 연결된 모든 사용자와 관련된 정보를 보유합니다. 이 예에서는 Azure 클라이언트 라이브러리를 사용하여 컨테이너 및 대상 그룹을 만듭니다.

다음 이미지는 ID 단추와 컨테이너 ID 입력 필드를 보여 줍니다. 컨테이너 ID 필드를 비워두고 사용자 ID 단추를 클릭하면 새 컨테이너가 만들어지고 선택된 사용자로 조인됩니다. 또는 최종 사용자는 컨테이너 ID를 입력하고 사용자 ID를 선택하여 기존 컨테이너에 선택한 사용자로 조인할 수 있습니다.

사용자 선택 단추가 있는 브라우저의 스크린샷.

다음 이미지는 상자로 표시된 컨테이너에 연결된 여러 사용자를 보여 줍니다. 파란색 윤곽선의 상자는 클라이언트를 보고 있는 사용자를 나타내고 검은색 윤곽선의 상자는 연결된 다른 사용자를 나타냅니다. 새로운 사용자가 고유 ID로 컨테이너에 연결되면 상자 수가 증가합니다.

4명의 다른 컨테이너 사용자에 대한 정보를 표시하는 브라우저의 스크린샷.

참고 항목

이 자습서에서는 사용자가 Fluid Framework 개요에 익숙하고 QuickStart를 완료했다고 가정합니다. 또한 React, React 프로젝트 만들기React Hooks의 기본 사항에 익숙해야 합니다.

프로젝트를 만듭니다.

  1. 명령 프롬프트를 열고 프로젝트를 만들 부모 폴더로 이동합니다. 예: C:\My Fluid Projects.

  2. 프롬프트에서 다음 명령을 실행합니다. (CLI는 npm이 아니라 npx입니다. Node.js를 설치할 때 설치되었습니다.)

    npx create-react-app fluid-audience-tutorial
    
  3. 프로젝트는 fluid-audience-tutorial이라는 하위 폴더에 만들어집니다. cd fluid-audience-tutorial 명령으로 이동합니다.

  4. 이 프로젝트는 다음 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
    

프로젝트 코딩

상태 변수 및 구성 요소 보기 설정

  1. 코드 편집기에서 \src\App.js 파일을 엽니다. 모든 기본 import 문을 삭제합니다. 그런 다음 return 문에서 모든 태그를 삭제합니다. 그런 다음 구성 요소 및 React 후크에 대한 가져오기 문을 추가합니다. 이후 단계에서 가져온 AudienceDisplayUserIdSelection 구성 요소를 구현할 예정입니다. 파일은 다음과 같습니다.

        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
        );
        }
    
  2. 다음 코드로 TODO 1을 교체합니다. 이 코드는 애플리케이션 내에서 사용될 로컬 상태 변수를 초기화합니다. displayAudience 값은 AudienceDisplay 구성 요소 또는 UserIdSelection 구성 요소를 렌더링할지 여부를 결정합니다(TODO 2 참조). userId 값은 컨테이너에 연결할 사용자 ID이고 containerId 값은 로드할 컨테이너입니다. handleSelectUserhandleContainerNotFound 함수는 두 보기에 대한 콜백으로 전달되고 상태 전환을 관리합니다. 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]);
    
  3. 다음 코드로 TODO 2을 교체합니다. 위에서 설명한 것처럼 displayAudience 변수는 AudienceDisplay 구성 요소 또는 UserIdSelection 구성 요소를 렌더링할지 여부를 결정합니다. 또한 상태 변수를 업데이트하는 함수는 속성으로 구성 요소에 전달됩니다.

        (displayAudience) ?
        <AudienceDisplay userId={userId} containerId={containerId} onContainerNotFound={handleContainerNotFound}/> :
        <UserIdSelection onSelectUser={handleSelectUser}/>
    

AudienceDisplay 구성 요소 설정

  1. 코드 편집기에서 \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 라이브러리에서 가져온 개체는 사용자 및 컨테이너를 정의하는 데 필요합니다. 다음 단계에서 AzureClientInsecureTokenProvider는 클라이언트 서비스를 구성하는 데 사용되며(TODO 1 참조), SharedMap은 컨테이너를 만드는 데 필요한 containerSchema를 구성하는 데 사용됩니다(TODO 2 참조).

  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
        }
    

    AudienceDisplayAudienceList는 대상 그룹 데이터 가져오기 및 렌더링을 처리하는 기능적 구성 요소이며 tryGetAudienceObject 메서드는 컨테이너 및 대상 그룹 서비스 만들기를 처리합니다.

컨테이너 및 대상 그룹 가져오기

도우미 함수를 사용하여 Audience 개체에서 보기 계층(React 상태)으로 Fluid 데이터를 가져올 수 있습니다. tryGetAudienceObject 메서드는 사용자 ID가 선택된 후 보기 구성 요소가 로드될 때 호출됩니다. 반환된 값은 React 상태 속성에 할당됩니다.

  1. 다음 코드로 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에 지시해야 합니다.

  1. 다음 코드로 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();
    
  2. 다음 코드로 TODO 3을 교체합니다. 이는 구성 요소가 탑재될 때 tryGetAudienceObject를 호출하고 반환된 대상 그룹 멤버를 fluidMemberscurrentMember로 설정합니다. 사용자가 존재하지 않는 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) };
        });
        }, []);
    
  3. 다음 코드로 TODO 4을 교체합니다. fluidMembers 또는 currentMember가 초기화되지 않은 경우 빈 화면이 렌더링됩니다. AudienceList 구성 요소는 스타일을 지정하여 멤버 데이터를 렌더링합니다(다음 섹션에서 구현 예정).

        if (!fluidMembers || !currentMember) return (<div/>);
    
        return (
            <AudienceList fluidMembers={fluidMembers} currentMember={currentMember}/>
        )
    

    참고 항목

    연결 전환으로 인해 getMyselfundefined를 반환하는 짧은 타이밍 창이 발생할 수 있습니다. 이는 현재 클라이언트 연결이 아직 대상 그룹에 추가되지 않았기 때문에 일치하는 연결 ID를 찾을 수 없기 때문입니다. React가 대상 그룹 멤버가 없는 페이지를 렌더링하는 것을 방지하기 위해 membersChanged에서 updateMembers를 호출하는 수신기를 추가합니다. 이는 컨테이너가 연결될 때 서비스 대상 그룹이 membersChanged 이벤트를 방출하기 때문에 작동합니다.

보기 만들기

  1. 다음 코드로 TODO 5을 교체합니다. AudienceDisplay 구성 요소에서 전달된 각 멤버에 대해 목록 구성 요소를 렌더링하고 있습니다. 각 멤버에 대해 먼저 member.userIdcurrentMember.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>
            );
        });
    
  2. 다음 코드로 TODO 6을 교체합니다. 이렇게 하면 list 배열에 푸시한 각 멤버 요소가 모두 렌더링됩니다.

        return (
            <div>
                {list}
            </div>
        );
    

UserIdSelection 구성 요소 설정

  1. 코드 편집기에서 \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
        );
    }
    
  2. 다음 코드로 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);
        };
    
  3. 다음 코드로 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 서버 시작 및 애플리케이션 실행

참고 항목

이 방법의 나머지 부분과 일치시키기 위해 이 섹션에서는 npxnpm 명령을 사용하여 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의 향후 릴리스에서 해결될 것입니다.

다음 단계

  • userConfigadditionalDetails 필드에서 더 많은 키/값 쌍으로 데모를 확장해 보세요.
  • SharedMap 또는 SharedString과 같은 분산 데이터 구조를 활용하는 협업 애플리케이션에 대상 그룹을 통합하는 것을 고려합니다.
  • 대상 그룹에 대해 자세히 알아봅니다.

코드를 변경하면 프로젝트가 자동으로 다시 빌드되고 애플리케이션 서버가 다시 로드됩니다. 그러나 컨테이너 스키마를 변경하면 애플리케이션 서버를 닫고 다시 시작해야만 적용됩니다. 이렇게 하려면 명령 프롬프트에 포커스를 두고 Ctrl-C를 두 번 누릅니다. 그런 다음 다시 npm run start를 실행합니다.