Como usar recursos de público-alvo no Fluid Framework

Neste tutorial, você aprenderá a usar o Público-alvo do Fluid Framework com React para criar uma demonstração visual de usuários que se conectam a um contêiner. O objeto audience contém informações relacionadas a todos os usuários conectados ao contêiner. Neste exemplo, a biblioteca de clientes do Azure será usada para criar o contêiner e o público-alvo.

A imagem a seguir mostra botões de ID e um campo de entrada de ID do contêiner. Deixar o campo de ID do contêiner em branco e clicar em um botão de ID de usuário criará um contêiner e ingressará como o usuário selecionado. Como alternativa, o usuário final pode inserir uma ID de contêiner e escolher uma ID de usuário para ingressar em um contêiner existente como o usuário selecionado.

Uma captura de tela de um navegador com botões para selecionar um usuário.

A próxima imagem mostra vários usuários conectados a um contêiner representado por caixas. A caixa contornada em azul representa o usuário que está exibindo o cliente, enquanto as caixas contornadas em preto representam os outros usuários conectados. À medida que novos usuários se anexam ao contêiner usando IDs exclusivas, o número de caixas aumenta.

Uma captura de tela de um navegador mostrando informações sobre quatro usuários de contêiner diferentes.

Observação

Este tutorial pressupõe que você tenha conhecimento da Visão geral do Fluid Framework e tenha concluído o Início Rápido. Você também precisa conhecer os conceitos básicos sobre o React, sobre criar projetos React e sobre Hooks React.

Criar o projeto

  1. Abra um prompt de comando e navegue até a pasta pai em que deseja criar o projeto, por exemplo, C:\My Fluid Projects.

  2. Execute o comando a seguir no prompt. (Observe que a CLI é npx, não npm. Ela foi instalada quando você instalou o Node.js.)

    npx create-react-app fluid-audience-tutorial
    
  3. O projeto é criado em uma subpasta chamada fluid-audience-tutorial. Navegue até ela usando o comando cd fluid-audience-tutorial.

  4. O projeto usa as seguintes bibliotecas Fluid:

    Biblioteca Descrição
    fluid-framework Contém a estrutura de dados distribuída SharedMap, que sincroniza dados entre clientes.
    @fluidframework/azure-client Define a conexão com um servidor do serviço Fluid e define o esquema inicial do contêiner Fluid.
    @fluidframework/test-client-utils Define o InsecureTokenProvider necessário para criar a conexão com um Serviço Fluid.

    Execute o comando a seguir para instalar as bibliotecas.

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

Codificar o projeto

Configurar variáveis de estado e exibição de componente

  1. Abra o arquivo \src\App.js no editor de código. Exclua todas as instruções import padrão. Em seguida, exclua toda a marcação da instrução return. Em seguida, adicione instruções de importação para componentes e hooks React. Observe que implementaremos os componentes AudienceDisplay e UserIdSelection importados nas etapas posteriores. O arquivo deverá ter a seguinte aparência:

        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. Substitua TODO 1 pelo código a seguir. Esse código inicializa variáveis de estado locais que serão usadas dentro do aplicativo. O valor displayAudience determina se renderizamos o componente AudienceDisplay ou UserIdSelection (consulte TODO 2). O valor userId é o identificador de usuário usado para se conectar ao contêiner, e o valor containerId é o contêiner a ser carregado. As funções handleSelectUser e handleContainerNotFound são passadas como retornos de chamada para as duas exibições e gerenciam transições de estado. handleSelectUser é chamado ao tentar criar/carregar um contêiner. handleContainerNotFound é chamado quando ocorre falha ao criar/carregar um contêiner.

    Observe que os valores de userId e containerId virão de um componente UserIdSelection por meio da função handleSelectUser.

        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. Substitua TODO 2 pelo código a seguir. Conforme indicado acima, a variável displayAudience determinará se renderizamos o componente AudienceDisplay ou UserIdSelection. Além disso, as funções para atualizar as variáveis de estado são passadas para os componentes como propriedades.

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

Configurar o componente AudienceDisplay

  1. Crie e abra um arquivo \src\AudienceDisplay.js no editor de código. Adicione as seguintes declarações de import :

        import { useEffect, useState } from "react";
        import { SharedMap } from "fluid-framework";
        import { AzureClient } from "@fluidframework/azure-client";
        import { InsecureTokenProvider } from "@fluidframework/test-client-utils";
    

    Observe que os objetos importados da biblioteca do Fluid Framework são necessários para definir usuários e contêineres. Nas etapas a seguir, AzureClient e InsecureTokenProvider serão usados para configurar o serviço do cliente (consulte TODO 1), enquanto SharedMap será usado para configurar um containerSchema necessário para criar um contêiner (consulte TODO 2).

  2. Adicione os seguintes componentes funcionais e funções auxiliares:

        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
        }
    

    Observe que AudienceDisplay e AudienceList são componentes funcionais que cuidam da obtenção e da renderização de dados de público-alvo, enquanto o método tryGetAudienceObject cuida da criação de serviços de contêiner e público-alvo.

Obtendo o contêiner e o público-alvo

Você pode usar uma função auxiliar para colocar os dados do Fluid, do objeto Audience, na camada de exibição (o estado React). O método tryGetAudienceObject é chamado quando o componente de exibição é carregado após uma ID de usuário ser selecionada. O valor retornado é atribuído a uma propriedade de estado React.

  1. Substitua TODO 1 pelo código a seguir. Observe que os valores para userId userName containerId serão passados do componente Aplicativo . Se não containerIdhouver , um novo contêiner será criado. Além disso, observe que o containerId é armazenado no hash da URL. Um usuário que entra em uma sessão de um novo navegador pode copiar a URL de um navegador de sessão existente ou navegar até localhost:3000 e inserir manualmente a ID do contêiner. Com essa implementação, queremos encapsular a getContainer chamada em um try catch no caso de o usuário inserir um ID de contêiner que não existe. Visite a documentação de contêineres para obter mais informações.

        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;
    

Obtendo o público-alvo na montagem do componente

Agora que definimos como obter o público-alvo do Fluid, precisamos instruir o React a chamar tryGetAudienceObject quando o componente de exibição de público-alvo for montado.

  1. Substitua TODO 2 pelo código a seguir. Observe que o ID do usuário virá do componente pai como ou user1 user2 random. Se a ID for random, usaremos Math.random() para gerar um número aleatório como a ID. Além disso, um nome será mapeado para o usuário com base na ID, conforme especificado em userNameList. Por fim, definimos as variáveis de estado, que armazenarão os membros conectados, bem como o usuário atual. fluidMembers armazenará uma lista de todos os membros conectados ao contêiner, enquanto currentMember conterá o objeto membro que representa o usuário atual que está exibindo o contexto do navegador.

        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. Substitua TODO 3 pelo código a seguir. Isso chamará o tryGetAudienceObject quando o componente for montado e definirá os membros do público-alvo retornados como fluidMembers e currentMember. Nota, verificamos se um objeto de audiência é retornado no caso de um usuário inserir um containerId que não existe e precisamos retorná-lo para a visualização UserIdSelection (props.onContainerNotFound() irá lidar com a alternância da exibição). Além disso, é uma boa prática cancelar o registro de manipuladores de eventos quando o componente React desmonta retornando 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. Substitua TODO 4 pelo código a seguir. Observe que, se fluidMembers ou currentMember não tiver sido inicializado, uma tela em branco será renderizada. O componente AudienceList renderizará os dados do membro com estilo (a ser implementado na próxima seção).

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

    Observação

    Transições de conexão podem resultar em janelas de tempo curto, em que getMyself retorna undefined. Isso ocorre porque a conexão de cliente atual ainda não foi adicionada ao público-alvo, portanto, não é possível encontrar uma ID de conexão correspondente. Para impedir que React renderize uma página sem membros do público-alvo, adicionamos um ouvinte para chamar updateMembers em membersChanged. Isso funciona porque o público-alvo do serviço emite um evento membersChanged quando o contêiner é conectado.

Criar a exibição

  1. Substitua TODO 5 pelo código a seguir. Observe que estamos renderizando um componente de lista para cada membro passado do componente AudienceDisplay. Para cada membro, primeiro comparamos member.userId a currentMember.userId para verificar se esse membro isSelf. Dessa forma, podemos diferenciar o usuário cliente dos outros usuários e exibir o componente com uma cor diferente. Em seguida, efetuamos push do componente de lista para uma matriz list. Cada componente exibirá dados de membro como userId userName e 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>
            );
        });
    
  2. Substitua TODO 6 pelo código a seguir. Isso renderizará todos os elementos membros dos quais efetuamos push para a matriz list.

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

Configurar o componente UserIdSelection

  1. Crie e abra um arquivo \src\UserIdSelection.js no editor de código. Esse componente incluirá botões de ID de usuário e campos de entrada de ID do contêiner, que permitem que os usuários finais escolham sua ID de usuário e sessão colaborativa. Adicione as seguintes instruções import e componentes funcionais:

    import { useState } from 'react';
    
    export const UserIdSelection = (props) => {
        // TODO 1: Define styles and handle user inputs
        return (
        // TODO 2: Return view components
        );
    }
    
  2. Substitua TODO 1 pelo código a seguir. Observe que a função onSelectUser atualizará as variáveis de estado no componente App pai e solicitará uma alteração de exibição. O método handleSubmit é disparado por elementos de botão que serão implementados em TODO 2. Além disso, o método handleChange é usado para atualizar a variável de estado containerId. Esse método será chamado de um ouvinte de evento do elemento de entrada implementado em TODO 2. Além disso, observe que atualizamos containerId obtendo o valor de um elemento HTML com a ID containerIdInput (definida em TODO 2).

        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. Substitua TODO 2 pelo código a seguir. Isso renderizará os botões de ID de usuário, bem como o campo de entrada da ID do contêiner.

        <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>
    

Iniciar o servidor Fluid e executar o aplicativo

Observação

Para corresponder ao restante deste guia de instruções, esta seção usa os comandos npx e npm para iniciar um servidor Fluid. No entanto, o código neste artigo também pode ser executado em um servidor do Azure Fluid Relay. Para saber mais, confira Como provisionar um serviço do Azure Fluid Relay e Como se conectar a um serviço do Azure Fluid Relay

No prompt de comando, execute o comando a seguir para iniciar o serviço Fluid.

npx @fluidframework/azure-local-service@latest

Abra um novo prompt de comando e navegue até a raiz do projeto, por exemplo, C:/My Fluid Projects/fluid-audience-tutorial. Inicie o servidor do aplicativo com o comando a seguir. O aplicativo é aberto no navegador. Isso pode levar alguns minutos.

npm run start

Navegue até localhost:3000 em uma guia do navegador para exibir o aplicativo em execução. Para criar um contêiner, selecione um botão de ID de usuário enquanto deixa a entrada da ID do contêiner em branco. Para simular um novo usuário que ingressa na sessão do contêiner, abra uma nova guia do navegador e navegue até localhost:3000. Desta vez, insira o valor da ID do contêiner, que pode ser encontrado na URL da primeira guia do navegador antes de http://localhost:3000/#.

Observação

Talvez seja necessário instalar uma dependência adicional para tornar essa demonstração compatível com o Webpack 5. Se você receber um erro de compilação relacionado a um pacote "buffer" ou "url", execute npm install -D buffer url e tente novamente. Isso será resolvido em uma versão futura do Fluid Framework.

Próximas etapas

  • Tente estender a demonstração com mais pares chave-valor no campo additionalDetails em userConfig.
  • Considere integrar o público-alvo a um aplicativo colaborativo que utiliza estruturas de dados distribuídas, como SharedMap ou SharedString.
  • Saiba mais sobre o Público-alvo.

Dica

Quando você fizer alterações no código, o projeto será recompilado automaticamente e o servidor de aplicativos será recarregado. No entanto, se você fizer alterações no esquema do contêiner, elas só entrarão em vigor se você fechar e reiniciar o servidor de aplicativos. Para fazer isso, focalize no prompt de comando e pressione Ctrl+C duas vezes. Em seguida, execute npm run start novamente.