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

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

若要跳转到已完成的演示,请查看 FluidExamples 存储库中的受众演示

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

浏览器的屏幕截图,其中显示了用于选择用户的按钮。

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

浏览器的屏幕截图,其中显示了四个不同容器用户的信息。

注意

本教程假设你熟悉 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 语句。 请注意,我们将在后面的步骤中实现导入的 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
        );
        }
    
  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 替换为以下代码。 请注意,userId userName containerId 的值将从“应用”组件传入。 如果没有 containerId,则会创建一个新容器。 另请注意,containerId 存储在 URL 哈希中。 从新浏览器进入会话的用户可以从现有会话浏览器中复制 URL,或导航到 localhost:3000 并手动输入容器 ID。 我们希望通过这种实现,将 getContainer 调用包装在 try catch 中,以防用户输入一个不存在的容器 ID。 有关详细信息,请访问 React 演示容器文档。

        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 将作为父组件 user1 user2random。 如果 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 数组。 每个组件将显示成员数据,例如 userId userNameadditionalDetails

        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