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.
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.
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
Abra um prompt de comando e navegue até a pasta pai em que deseja criar o projeto, por exemplo,
C:\My Fluid Projects
.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
O projeto é criado em uma subpasta chamada
fluid-audience-tutorial
. Navegue até ela usando o comandocd fluid-audience-tutorial
.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
Abra o arquivo
\src\App.js
no editor de código. Exclua todas as instruçõesimport
padrão. Em seguida, exclua toda a marcação da instruçãoreturn
. 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 ); }
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 valordisplayAudience
determina se renderizamos o componente AudienceDisplay ou UserIdSelection (consulteTODO 2
). O valoruserId
é o identificador de usuário usado para se conectar ao contêiner, e o valorcontainerId
é o contêiner a ser carregado. As funçõeshandleSelectUser
ehandleContainerNotFound
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]);
Substitua
TODO 2
pelo código a seguir. Conforme indicado acima, a variáveldisplayAudience
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
Crie e abra um arquivo
\src\AudienceDisplay.js
no editor de código. Adicione as seguintes declarações deimport
: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 umcontainerSchema
necessário para criar um contêiner (consulteTODO 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.
Substitua
TODO 1
pelo código a seguir. Observe que os valores parauserId
userName
containerId
serão passados do componente Aplicativo . Se nãocontainerId
houver , um novo contêiner será criado. Além disso, observe que ocontainerId
é 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 agetContainer
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.
Substitua
TODO 2
pelo código a seguir. Observe que o ID do usuário virá do componente pai como ouuser1
user2
random
. Se a ID forrandom
, usaremosMath.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 emuserNameList
. 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, enquantocurrentMember
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();
Substitua
TODO 3
pelo código a seguir. Isso chamará otryGetAudienceObject
quando o componente for montado e definirá os membros do público-alvo retornados comofluidMembers
ecurrentMember
. 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 retornandoaudience.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) }; }); }, []);
Substitua
TODO 4
pelo código a seguir. Observe que, sefluidMembers
oucurrentMember
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
retornaundefined
. 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 chamarupdateMembers
emmembersChanged
. Isso funciona porque o público-alvo do serviço emite um eventomembersChanged
quando o contêiner é conectado.
Criar a exibição
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 comparamosmember.userId
acurrentMember.userId
para verificar se esse membroisSelf
. 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 matrizlist
. Cada componente exibirá dados de membro comouserId
userName
eadditionalDetails
.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> ); });
Substitua
TODO 6
pelo código a seguir. Isso renderizará todos os elementos membros dos quais efetuamos push para a matrizlist
.return ( <div> {list} </div> );
Configurar o componente UserIdSelection
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çõesimport
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 ); }
Substitua
TODO 1
pelo código a seguir. Observe que a funçãoonSelectUser
atualizará as variáveis de estado no componente App pai e solicitará uma alteração de exibição. O métodohandleSubmit
é disparado por elementos de botão que serão implementados emTODO 2
. Além disso, o métodohandleChange
é usado para atualizar a variável de estadocontainerId
. Esse método será chamado de um ouvinte de evento do elemento de entrada implementado emTODO 2
. Além disso, observe que atualizamoscontainerId
obtendo o valor de um elemento HTML com a IDcontainerIdInput
(definida emTODO 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); };
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
emuserConfig
. - 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.