你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

操作说明:在 Fluid Framework 中使用受众功能

本教程介绍如何将 Fluid Framework 受众React 结合使用来创建用户连接到容器的视觉演示。 受众对象保存有关连接到容器的所有用户的信息。 在此示例中,Azure 客户端库将用于创建容器和受众。

下图显示了 ID 按钮和容器 ID 输入字段。 将容器 ID 字段留空并单击用户 ID 按钮会创建一个新容器并以选定用户的身份加入该容器。 或者,最终用户可以输入容器 ID,然后选择用户 ID,以便以选定用户的身份加入现有容器。

A screenshot of a browser with buttons for selecting a user.

下图显示了已连接到容器的多个用户(由方框表示)。 蓝色框表示正在查看客户端的用户,而黑色框表示其他已连接的用户。 随着新用户使用唯一 ID 连接到该容器,方框的数量将会增加。

A screenshot of a browser showing information for four different container users.

注意

本教程假设你熟悉 Fluid Framework 概述并已完成快速入门。 你还应该熟悉 React创建 React 项目React 挂钩的基础知识。

创建项目

  1. 打开命令提示符并导航到要在其中创建项目的父文件夹,例如 C:\My Fluid Projects

  2. 在提示符下运行以下命令。 (请注意,CLI 是 npx 而不是 npm。它是在安装 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 服务创建连接所需的 InsecureTokenProvider

    运行以下命令以安装库。

    npm install @fluidframework/azure-client @fluidframework/test-client-utils fluid-framework
    

为项目编写代码

设置状态变量和组件视图

  1. 在代码编辑器中打开文件 \src\App.js。 删除所有默认的 import 语句。 然后从 return 语句中删除所有标记。 然后为组件和 React 挂钩添加 import 语句。 请注意,我们将在后续步骤中实现导入 的 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 2userId 值是用于连接到容器的用户标识符,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 库中导入的对象来定义用户和容器。 在以下步骤中,AzureClient 和 InsecureTokenProvider 用于配置客户端服务(参阅 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
        }
    

    请注意,AudienceDisplay 和 AudienceList 是用于处理受众数据的获取和呈现的功能组件,而 tryGetAudienceObject 方法处理容器和受众服务的创建

获取容器和受众

可以使用一个帮助器函数将 Fluid 数据从受众对象提取到视图层(React 状态)中。 在选择用户 ID 后加载视图组件时将调用 tryGetAudienceObject 方法。 返回值将分配到 React 状态属性。

  1. TODO 1 替换为以下代码。 请注意,userIduserNamecontainerId 的值将从 App 组件传入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 受众后,我们需要告知 React 在装载受众显示组件时调用 tryGetAudienceObject

  1. TODO 2 替换为以下代码。 请注意,用户 ID 将作为 user1user2random 从父组件传递。 如果 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()将处理切换视图),则检查如果返回访问群体对象。 此外,良好的做法是当 React 组件通过返回 audience.off 卸除时取消注册事件处理程序。

        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 替换为以下代码。 请注意,如果 fluidMemberscurrentMember 尚未初始化,则会呈现空白屏幕。 AudienceList 组件将呈现带样式的成员数据(将在下一部分实现)

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

    注意

    连接转换可能导致较短的计时窗口,其中的 getMyself 返回 undefined。 这是因为当前客户端连接尚未添加到受众,因此找不到匹配的连接 ID。 为了防止 React 呈现一个不包含受众成员的页面,我们添加了一个侦听器来对 membersChanged 调用 updateMembers。 这是有效的做法,因为服务受众在连接容器时发出 membersChanged 事件。

创建该视图

  1. TODO 5 替换为以下代码。 请注意,我们将为从 AudienceDisplay 组件传递的每个成员呈现一个列表组件。 对于每个成员,我们首先将 member.userIdcurrentMember.userId 进行比较以检查该成员是否为 isSelf。 这样,我们就可以将客户端用户与其他用户区分开来,并以不同的颜色显示组件。 然后将列表组件推送到 list 数组。 每个组件将显示成员数据,例如 userIduserNameadditionalDetails

        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 函数将更新父 App 组件中的状态变量并提示视图更改handleSubmit 方法由 TODO 2 中实现的按钮元素触发。 此外,handleChange 方法用于更新 containerId 状态变量。 此方法由 TODO 2 中实现的输入元素事件侦听器调用。 另请注意,我们将 containerId 更新为从 ID 为 containerIdInput(在 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);
        };
    
  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。 这一次,请输入容器 ID 值,该值可以从第一个浏览器标签页的 URL 中找到,其前面是 http://localhost:3000/#

注意

可能需要安装一个附加的依赖项才能使此演示与 Webpack 5 兼容。 如果收到与“buffer”或“url”包相关的编译错误,请运行 npm install -D buffer url 并重试。 此错误会在将来的 Fluid Framework 版本中得到解决。

后续步骤

  • 尝试在 userConfigadditionalDetails 字段中使用更多键/值对来扩展该演示。
  • 考虑将受众集成到利用分布式数据结构(例如 SharedMap 或 SharedString)的协作应用程序中。
  • 详细了解受众

提示

对代码进行更改时,项目将自动重新生成,并且应用程序服务器将重新加载。 但是,如果对容器架构进行更改,只有在关闭再重启应用程序服务器后,这些更改才会生效。 为此,请将焦点移至命令提示符并按 Ctrl-C 两次。 然后再次运行 npm run start