---
title: 使用Parse React Hook构建实时聊天应用程序示例
slug: docs-ch/react/real-time/react-chat-app
description: 学习使用Parse React钩子构建实时聊天应用，实现实时查询功能。
image: https://www.back4app.com/images/logo-social.png
createdAt: 2024-03-19T18:26:56.427Z
---

# React 聊天应用 - 实时

## 介绍

在上一个指南中，您了解了更多关于 <font color="#2166ae">`@parse/react` </font>助手库，该库可以快速在您的 React 应用程序中启用 Parse 实时查询支持。该库完全用 Typescript 编写，基于 [Parse Javascript SDK](https://www.npmjs.com/package/parse), 目前处于 Alpha 版本。

现在，在本指南中，您将使用 Parse React 钩子在创建一个简单的实时聊天应用程序的实际情况下。该应用程序由两个 React 组件组成，突出了 Parse 的实时查询的实用性，并展示了您创建完整实时应用所需了解的一切。

:::hint{type="danger"}
Parse React Native 目前处于 Alpha 版本。该库正在测试中，因此我们建议谨慎使用。我们非常感谢您的反馈，因此请随时使用该库，并通过发送电子邮件至 community\@back4app.com 向我们提出您的问题和初步印象。
:::

## 先决条件

:::hint{type="info"}
要完成本教程，您需要：


- 一个已创建并 [连接到 Back4App](https://www.back4app.com/docs/react/quickstart).
- 完成之前的指南，以便更好地理解 [Parse React 钩子和实时查询](https://www.back4app.com/docs/react/ral-time/react-real-time-hoook).
- 如果您想测试/使用本指南提供的屏幕布局，您应该设置 [<font color="#2166ae">`Ant Design`</font> 库](https://ant.design/docs/react/introduce).
:::

## 目标

使用 <font color="#2166ae">`@parse/react`</font> 钩子作为 Parse 上实时查询的启用器，构建一个实时聊天应用程序。

## 1 - 理解和创建数据库类

聊天应用程序将由两个小数据库类组成： <font color="#2166ae">`昵称`</font> 和 <font color="#2166ae">`消息`</font>. <font color="#2166ae">`昵称`</font> 只有一个名为 <font color="#2166ae">`名称` </font>的文本字段，并将代表应用程序中的用户。 <font color="#2166ae">`消息`</font> 将保存两个用户之间发送的任何文本消息，因此它需要有一个名为 <font color="#2166ae">`文本`</font> 的文本字段和两个对象指针字段，分别称为 <font color="#2166ae">`发送者`</font> 和 <font color="#2166ae">`接收者`</font>, 两者都与 <font color="#2166ae">`昵称`</font> 类相关。

在您的 Back4App 仪表板的 Javascript 控制台上运行以下代码片段，以创建这些类并用一些示例填充它们。

```javascript
1	// Create Nicknames
2	let NicknameA = new Parse.Object('Nickname');
3	NicknameA.set('name', 'SmartBoy22');
4	NicknameA = await NicknameA.save();
5	let NicknameB = new Parse.Object('Nickname');
6	NicknameB.set('name', 'CleverGirl23');
7	NicknameB = await NicknameB.save();
8	
9	// Create Message linked to Nicknames
10	let Message = new Parse.Object('Message');
11	Message.set('text', 'Hi! How are you?');
12	Message.set('sender', NicknameA);
13	Message.set('receiver', NicknameA);
14	Message = await Message.save();
15	console.log('success');
```

## 2 - 启用实时查询

现在您已经创建了 <font color="#2166ae">`昵称` </font>和 <font color="#2166ae">`消息` </font>类，我们需要为它们启用实时查询功能。请转到您的 Back4App 仪表板并导航到 <font color="#2166ae">`应用设置`</font> ><font color="#2166ae"> `服务器设置`</font> > <font color="#2166ae">`服务器 URL 和实时查询`</font>. 在激活您的 Back4App 子域后，您可以激活实时查询并选择哪些数据库类将启用它。确保选择新类并保存更改。

![](https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/t7sc31kFzneS0eVyTZLU6_image.png)

接下来要做的是创建我们的聊天应用程序，它由两个组件组成， <font color="#2166ae">`ChatSetup` </font>和 <font color="#2166ae">`LiveChat`</font>.

## 3 - 创建聊天设置组件

这个组件负责创建和设置发送者和接收者 <font color="#2166ae">`昵称`</font> 对象，同时也作为 <font color="#2166ae">`LiveChat` </font>组件结构的容器。设置部分的布局只有两个用于昵称的输入字段和一个触发设置功能的按钮。在你的 <font color="#2166ae">`src` </font> 目录中创建一个名为 <font color="#2166ae">`ChatSetup.js`</font> （或 <font color="#2166ae">`ChatSetup.tsx`</font>) 并使用以下代码:

:::CodeblockTabs
ChatSetup.js

```javascript
1	import React, { useState } from "react";
2	import "./App.css";
3	import { Button, Input } from "antd";
4	import Parse from "parse";
5	import { LiveChat } from "./LiveChat";
6	
7	export const ChatSetup = () => {
8	  // State variables holding input values and results
9	  const [senderNicknameInput, setSenderNicknameInput] = useState("");
10	  const [senderNicknameId, setSenderNicknameId] = useState(null);
11	  const [receiverNicknameInput, setReceiverNicknameInput] = useState("");
12	  const [receiverNicknameId, setReceiverNicknameId] = useState(null);
13	
14	  // Create or retrieve Nickname objects and start LiveChat component
15	  const startLiveChat = async () => {
16	    const senderNicknameName = senderNicknameInput;
17	    const receiverNicknameName = receiverNicknameInput;
18	
19	    // Check if user informed both nicknames
20	    if (senderNicknameName === null || receiverNicknameName === null) {
21	      alert("Please inform both sender and receiver nicknames!");
22	      return false;
23	    }
24	
25	    // Check if sender nickname already exists, if not create new parse object
26	    let senderNicknameObject = null;
27	    try {
28	      const senderParseQuery = new Parse.Query("Nickname");
29	      senderParseQuery.equalTo("name", senderNicknameName);
30	      const senderParseQueryResult = await senderParseQuery.first();
31	      if (
32	        senderParseQueryResult !== undefined &&
33	        senderParseQueryResult !== null
34	      ) {
35	        senderNicknameObject = senderParseQueryResult;
36	      } else {
37	        senderNicknameObject = new Parse.Object("Nickname");
38	        senderNicknameObject.set("name", senderNicknameName);
39	        senderNicknameObject = await senderNicknameObject.save();
40	      }
41	    } catch (error) {
42	      alert(error);
43	      return false;
44	    }
45	
46	    // Check if receiver nickname already exists, if not create new parse object
47	    let receiverNicknameObject = null;
48	    try {
49	      const receiverParseQuery = new Parse.Query("Nickname");
50	      receiverParseQuery.equalTo("name", receiverNicknameName);
51	      const receiverParseQueryResult = await receiverParseQuery.first();
52	      if (
53	        receiverParseQueryResult !== undefined &&
54	        receiverParseQueryResult !== null
55	      ) {
56	        receiverNicknameObject = receiverParseQueryResult;
57	      } else {
58	        receiverNicknameObject = new Parse.Object("Nickname");
59	        receiverNicknameObject.set("name", receiverNicknameName);
60	        receiverNicknameObject = await receiverNicknameObject.save();
61	      }
62	    } catch (error) {
63	      alert(error);
64	      return false;
65	    }
66	
67	    // Set nickname objects ids, so live chat component is instantiated
68	    setSenderNicknameId(senderNicknameObject.id);
69	    setReceiverNicknameId(receiverNicknameObject.id);
70	    return true;
71	  };
72	
73	  return (
74	    <div>
75	      <div className="header">
76	        <img
77	          className="header_logo"
78	          alt="Back4App Logo"
79	          src={
80	            "https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png"
81	          }
82	        />
83	        <p className="header_text_bold">{"React on Back4App"}</p>
84	        <p className="header_text">{"Live query chat app"}</p>
85	      </div>
86	      <div className="container">
87	        {senderNicknameId === null && receiverNicknameId === null && (
88	          <div>
89	            <Input
90	              className="form_input"
91	              value={senderNicknameInput}
92	              onChange={(event) => setSenderNicknameInput(event.target.value)}
93	              placeholder={"Sender (Your) Nickname"}
94	              size="large"
95	            />
96	            <Input
97	              className="form_input"
98	              value={receiverNicknameInput}
99	              onChange={(event) => setReceiverNicknameInput(event.target.value)}
100	              placeholder={"Receiver (Their) Nickname"}
101	              size="large"
102	            />
103	            <Button
104	              type="primary"
105	              className="form_button"
106	              color={"#208AEC"}
107	              size={"large"}
108	              onClick={startLiveChat}
109	            >
110	              Start live chat
111	            </Button>
112	          </div>
113	        )}
114	        {senderNicknameId !== null && receiverNicknameId !== null && (
115	          <LiveChat
116	            senderNicknameName={senderNicknameInput}
117	            senderNicknameId={senderNicknameId}
118	            receiverNicknameName={receiverNicknameInput}
119	            receiverNicknameId={receiverNicknameId}
120	          />
121	        )}
122	      </div>
123	    </div>
124	  );
125	};
```

ChatSetup.tsx

```typescript
1	import React, { useState, FC, ReactElement } from "react";
2	import "./App.css";
3	import { Button, Input } from "antd";
4	import { LiveChat } from "./LiveChat";
5	import Parse from "parse";
6	
7	export const ChatSetup: FC<{}> = (): ReactElement => {
8	  // State variables holding input values and results
9	  const [senderNicknameInput, setSenderNicknameInput] = useState("");
10	  const [senderNicknameId, setSenderNicknameId] = useState<string | null>(null);
11	  const [receiverNicknameInput, setReceiverNicknameInput] = useState("");
12	  const [receiverNicknameId, setReceiverNicknameId] = useState<string | null>(null);
13	
14	  // Create or retrieve Nickname objects and start LiveChat component
15	  const startLiveChat = async (): Promise<Boolean> => {
16	    const senderNicknameName: string = senderNicknameInput;
17	    const receiverNicknameName: string = receiverNicknameInput;
18	
19	    // Check if user informed both nicknames
20	    if (senderNicknameName === "" || receiverNicknameName === "") {
21	      alert("Please inform both sender and receiver nicknames!");
22	      return false;
23	    }
24	
25	    // Check if sender nickname already exists, if not create new parse object
26	    let senderNicknameObject: Parse.Object | null = null;
27	    try {
28	      const senderParseQuery: Parse.Query = new Parse.Query("Nickname");
29	      senderParseQuery.equalTo("name", senderNicknameName);
30	      const senderParseQueryResult: Parse.Object | undefined = await senderParseQuery.first();
31	      if (
32	        senderParseQueryResult !== undefined
33	      ) {
34	        senderNicknameObject = senderParseQueryResult;
35	      } else {
36	        senderNicknameObject = new Parse.Object("Nickname");
37	        if (senderNicknameObject !== null) {
38	          senderNicknameObject.set("name", senderNicknameName);
39	          senderNicknameObject = await senderNicknameObject.save();
40	        }
41	      }
42	    } catch (error) {
43	      alert(error);
44	      return false;
45	    }
46	
47	    // Check if receiver nickname already exists, if not create new parse object
48	    let receiverNicknameObject: Parse.Object | null = null;
49	    try {
50	      const receiverParseQuery: Parse.Query = new Parse.Query("Nickname");
51	      receiverParseQuery.equalTo("name", receiverNicknameName);
52	      const receiverParseQueryResult: Parse.Object | undefined = await receiverParseQuery.first();
53	      if (
54	        receiverParseQueryResult !== undefined
55	      ) {
56	        receiverNicknameObject = receiverParseQueryResult;
57	      } else {
58	        receiverNicknameObject = new Parse.Object("Nickname");
59	        if (receiverNicknameObject !== null) {
60	          receiverNicknameObject.set("name", receiverNicknameName);
61	          receiverNicknameObject = await receiverNicknameObject.save();
62	        }
63	      }
64	    } catch (error: any) {
65	      alert(error);
66	      return false;
67	    }
68	
69	    // Set nickname objects ids, so live chat component is instantiated
70	    if (senderNicknameObject !== null && receiverNicknameObject !== null) {
71	      setSenderNicknameId(senderNicknameObject.id);
72	      setReceiverNicknameId(receiverNicknameObject.id);
73	    }
74	    return true;
75	  };
76	
77	  return (
78	    <div>
79	      <div className="header">
80	        <img
81	          className="header_logo"
82	          alt="Back4App Logo"
83	          src={
84	            "https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png"
85	          }
86	        />
87	        <p className="header_text_bold">{"React on Back4App"}</p>
88	        <p className="header_text">{"Live query chat app"}</p>
89	      </div>
90	      <div className="container">
91	        {senderNicknameId === null && receiverNicknameId === null && (
92	          <div>
93	            <Input
94	              className="form_input"
95	              value={senderNicknameInput}
96	              onChange={(event) => setSenderNicknameInput(event.target.value)}
97	              placeholder={"Sender (Your) Nickname"}
98	              size="large"
99	            />
100	            <Input
101	              className="form_input"
102	              value={receiverNicknameInput}
103	              onChange={(event) => setReceiverNicknameInput(event.target.value)}
104	              placeholder={"Receiver (Their) Nickname"}
105	              size="large"
106	            />
107	            <Button
108	              type="primary"
109	              className="form_button"
110	              color={"#208AEC"}
111	              size={"large"}
112	              onClick={startLiveChat}
113	            >
114	              Start live chat
115	            </Button>
116	          </div>
117	        )}
118	        {senderNicknameId !== null && receiverNicknameId !== null && (
119	          <LiveChat
120	            senderNicknameName={senderNicknameInput}
121	            senderNicknameId={senderNicknameId}
122	            receiverNicknameName={receiverNicknameInput}
123	            receiverNicknameId={receiverNicknameId}
124	          />
125	        )}
126	      </div>
127	    </div>
128	  );
129	};
```
:::

请注意，<font color="#2166ae">`LiveChat`</font> 组件仅在设置过程成功且所有状态变量正确设置时初始化和渲染。同样，设置输入在过程结束后被隐藏，子组件布局被渲染。

## 4 - 创建实时聊天组件

该 <font color="#2166ae">`LiveChat`</font> 组件处理展示和发送 <font color="#2166ae">`消息`</font> 在两个 <font color="#2166ae">`昵称`</font> 作为参数传递给它的初始化。正是在这个组件中，您将最终使用 <font color="#2166ae">`useParseQuery`</font> 钩子来自 <font color="#2166ae">`@parse/react`</font> 来设置将检索与此聊天实例相关的任何 <font color="#2166ae">`消息`</font> 对象的实时查询。在您的 <font color="#2166ae">`src`</font> 目录中创建一个名为<font color="#2166ae"> `LiveChat.js`</font> （或 <font color="#2166ae">`LiveChat.tsx`</font>) 的新文件，并插入以下代码。

:::CodeblockTabs
LiveChat.js

```javascript
1	import React, { useState } from "react";
2	import "./App.css";
3	import { Button, Input, Tooltip } from "antd";
4	import { SyncOutlined } from "@ant-design/icons";
5	import Parse from "parse";
6	import { useParseQuery } from "@parse/react";
7	
8	export const LiveChat = (props) => {
9	  // State variable to hold message text input
10	  const [messageInput, setMessageInput] = useState("");
11	
12	  // Create parse query for live querying using useParseQuery hook
13	  const parseQuery = new Parse.Query("Message");
14	  // Get messages that involve both nicknames
15	  parseQuery.containedIn("sender", [
16	    props.senderNicknameId,
17	    props.receiverNicknameId,
18	  ]);
19	  parseQuery.containedIn("receiver", [
20	    props.senderNicknameId,
21	    props.receiverNicknameId,
22	  ]);
23	  // Set results ordering
24	  parseQuery.ascending("createdAt");
25	
26	  // Include nickname fields, to enable name getting on list
27	  parseQuery.includeAll();
28	
29	  // Declare hook and variables to hold hook responses
30	  const { isLive, isLoading, isSyncing, results, count, error, reload } =
31	    useParseQuery(parseQuery, {
32	      enableLocalDatastore: true, // Enables cache in local datastore (default: true)
33	      enableLiveQuery: true, // Enables live query for real-time update (default: true)
34	    });
35	
36	  // Message sender handler
37	  const sendMessage = async () => {
38	    try {
39	      const messageText = messageInput;
40	
41	      // Get sender and receiver nickname Parse objects
42	      const senderNicknameObjectQuery = new Parse.Query("Nickname");
43	      senderNicknameObjectQuery.equalTo("objectId", props.senderNicknameId);
44	      let senderNicknameObject = await senderNicknameObjectQuery.first();
45	      const receiverNicknameObjectQuery = new Parse.Query("Nickname");
46	      receiverNicknameObjectQuery.equalTo("objectId", props.receiverNicknameId);
47	      let receiverNicknameObject = await receiverNicknameObjectQuery.first();
48	
49	      // Create new Message object and save it
50	      let Message = new Parse.Object("Message");
51	      Message.set("text", messageText);
52	      Message.set("sender", senderNicknameObject);
53	      Message.set("receiver", receiverNicknameObject);
54	      Message.save();
55	
56	      // Clear input
57	      setMessageInput("");
58	    } catch (error) {
59	      alert(error);
60	    }
61	  };
62	
63	  // Helper to format createdAt value on Message
64	  const formatDateToTime = (date) => {
65	    return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
66	  };
67	
68	  return (
69	    <div>
70	      <div className="flex_between">
71	        <h2 class="list_heading">{`${props.senderNicknameName} sending, ${props.receiverNicknameName} receiving!`}</h2>
72	        <Tooltip title="Reload">
73	          <Button
74	            onClick={reload}
75	            type="primary"
76	            shape="circle"
77	            icon={<SyncOutlined />}
78	          />
79	        </Tooltip>
80	      </div>
81	      {results && (
82	        <div className="messages">
83	          {results
84	            .sort((a, b) => a.get("createdAt") > b.get("createdAt"))
85	            .map((result) => (
86	              <div
87	                key={result.id}
88	                className={
89	                  result.get("sender").id === props.senderNicknameId
90	                    ? "message_sent"
91	                    : "message_received"
92	                }
93	              >
94	                <p className="message_bubble">{result.get("text")}</p>
95	                <p className="message_time">
96	                  {formatDateToTime(result.get("createdAt"))}
97	                </p>
98	                <p className="message_name">
99	                  {result.get("sender").get("name")}
100	                </p>
101	              </div>
102	            ))}
103	        </div>
104	      )}
105	      <div className="new_message">
106	        <h2 className="new_message_title">New message</h2>
107	        <Input
108	          className="form_input"
109	          value={messageInput}
110	          onChange={(event) => setMessageInput(event.target.value)}
111	          placeholder={"Your message..."}
112	          size="large"
113	        />
114	        <Button
115	          type="primary"
116	          className="form_button"
117	          color={"#208AEC"}
118	          size={"large"}
119	          onClick={sendMessage}
120	        >
121	          Send message
122	        </Button>
123	      </div>
124	      <div>
125	        {isLoading && <p>{"Loading…"}</p>}
126	        {isSyncing && <p>{"Syncing…"}</p>}
127	        {isLive ? <p>{"Status: Live"}</p> : <p>{"Status: Offline"}</p>}
128	        {error && <p>{error.message}</p>}
129	        {count && <p>{`Count: ${count}`}</p>}
130	      </div>
131	    </div>
132	  );
133	};
```

LiveChat.tsx

```typescript
1	import React, { useState, FC, ReactElement } from "react";
2	import "./App.css";
3	import { Button, Input, Tooltip } from "antd";
4	import { SyncOutlined } from "@ant-design/icons";
5	import Parse from "parse";
6	import { useParseQuery } from  "@parse/react";
7	
8	type LiveChatProps = {
9	  senderNicknameId: string,
10	  senderNicknameName: string,
11	  receiverNicknameId: string,
12	  receiverNicknameName: string,
13	}
14	
15	export const LiveChat: FC<LiveChatProps> = (props: LiveChatProps): ReactElement => {
16	  const Parse = require("parse/dist/parse.min.js");
17	    // State variable to hold message text input
18	  const [messageInput, setMessageInput] = useState("");
19	
20	  // Create parse query for live querying using useParseQuery hook
21	  const parseQuery: Parse.Query = new Parse.Query("Message");
22	  // Get messages that involve both nicknames
23	  parseQuery.containedIn("sender", [
24	    props.senderNicknameId,
25	    props.receiverNicknameId,
26	  ]);
27	  parseQuery.containedIn("receiver", [
28	    props.senderNicknameId,
29	    props.receiverNicknameId,
30	  ]);
31	  // Set results ordering
32	  parseQuery.ascending("createdAt");
33	
34	  // Include nickname fields, to enable name getting on list
35	  parseQuery.includeAll();
36	
37	  // Declare hook and variables to hold hook responses
38	  const { isLive, isLoading, isSyncing, results, count, error, reload } =
39	    useParseQuery(parseQuery, {
40	      enableLocalDatastore: true, // Enables cache in local datastore (default: true)
41	      enableLiveQuery: true, // Enables live query for real-time update (default: true)
42	    });
43	
44	  // Message sender handler
45	  const sendMessage = async () => {
46	    try {
47	      const messageText: string = messageInput;
48	
49	      // Get sender and receiver nickname Parse objects
50	      const senderNicknameObjectQuery: Parse.Query = new Parse.Query("Nickname");
51	      senderNicknameObjectQuery.equalTo("objectId", props.senderNicknameId);
52	      let senderNicknameObject: Parse.Object | undefined = await senderNicknameObjectQuery.first();
53	      const receiverNicknameObjectQuery: Parse.Query = new Parse.Query("Nickname");
54	      receiverNicknameObjectQuery.equalTo("objectId", props.receiverNicknameId);
55	      let receiverNicknameObject: Parse.Object | undefined = await receiverNicknameObjectQuery.first();
56	
57	      // Create new Message object and save it
58	      let Message: Parse.Object = new Parse.Object("Message");
59	      Message.set("text", messageText);
60	      Message.set("sender", senderNicknameObject);
61	      Message.set("receiver", receiverNicknameObject);
62	      Message.save();
63	
64	      // Clear input
65	      setMessageInput("");
66	    } catch (error: any) {
67	      alert(error);
68	    }
69	  };
70	
71	  // Helper to format createdAt value on Message
72	  const formatDateToTime = (date: Date): string => {
73	    return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
74	  };
75	
76	  return (
77	    <div>
78	      <div className="flex_between">
79	        <h2 class="list_heading">{`${props.senderNicknameName} sending, ${props.receiverNicknameName} receiving!`}</h2>
80	        <Tooltip title="Reload">
81	          <Button
82	            onClick={reload}
83	            type="primary"
84	            shape="circle"
85	            icon={<SyncOutlined />}
86	          />
87	        </Tooltip>
88	      </div>
89	      {results && (
90	        <div className="messages">
91	          {results
92	            .sort((a, b) => a.get("createdAt") > b.get("createdAt"))
93	            .map((result) => (
94	              <div
95	                key={result.id}
96	                className={
97	                  result.get("sender").id === props.senderNicknameId
98	                    ? "message_sent"
99	                    : "message_received"
100	                }
101	              >
102	                <p className="message_bubble">{result.get("text")}</p>
103	                <p className="message_time">
104	                  {formatDateToTime(result.get("createdAt"))}
105	                </p>
106	                <p className="message_name">
107	                  {result.get("sender").get("name")}
108	                </p>
109	              </div>
110	            ))}
111	        </div>
112	      )}
113	      <div className="new_message">
114	        <h2 className="new_message_title">New message</h2>
115	        <Input
116	          className="form_input"
117	          value={messageInput}
118	          onChange={(event) => setMessageInput(event.target.value)}
119	          placeholder={"Your message..."}
120	          size="large"
121	        />
122	        <Button
123	          type="primary"
124	          className="form_button"
125	          color={"#208AEC"}
126	          size={"large"}
127	          onClick={sendMessage}
128	        >
129	          Send message
130	        </Button>
131	      </div>
132	      <div>
133	        {isLoading && <p>{"Loading…"}</p>}
134	        {isSyncing && <p>{"Syncing…"}</p>}
135	        {isLive ? <p>{"Status: Live"}</p> : <p>{"Status: Offline"}</p>}
136	        {error && <p>{error.message}</p>}
137	        {count && <p>{`Count: ${count}`}</p>}
138	      </div>
139	    </div>
140	  );
141	};
```
:::

让我们将这个组件结构分解为四个部分，以便您更好地理解其布局：


- 在顶部，我们有消息的 <font color="#2166ae">`Parse.Query`</font> 和 Parse React hook 设置。在这里，您可以看到 <font color="#2166ae">`props`</font> 参数是如何被用来使查询能够检索我们想要的消息；
- 之后，您有 <font color="#2166ae">`sendMessage`</font> 函数，它将创建一个新的 <font color="#2166ae">`Message`</font> 对象，将其与此聊天实例中使用的 <font color="#2166ae">`Nickname` </font> 关联。还有一个帮助函数用于格式化消息日期值；
- 现在，在 JSX 代码中，我们有与 Parse React hook 变量相关的状态标志，以及连接重载按钮；
- 最后，您可以看到 <font color="#2166ae">`Message`</font> 列表，其中渲染的列表项样式由其发送者值决定。在底部，我们有消息发送部分，包含一个简单的文本输入和一个按钮。

最后，如果您想完全渲染组件布局，请将这些类添加到您的 <font color="#2166ae">`App.css`</font> 文件中，让我们继续测试我们的应用程序。

:::CodeblockTabs
App.css

```css
1	@import '~antd/dist/antd.css';
2	
3	html {
4	  box-sizing: border-box;
5	  outline: none;
6	  overflow: auto;
7	}
8	
9	body {
10	  margin: 0;
11	  background-color: #fff;
12	}
13	
14	*,
15	*:before,
16	*:after {
17	  margin: 0;
18	  padding: 0;
19	  box-sizing: inherit;
20	}
21	
22	h1,
23	h2,
24	h3,
25	h4,
26	h5,
27	h6 {
28	  margin: 0;
29	  font-weight: bold;
30	}
31	
32	li {
33	  list-style: none;
34	}
35	
36	p {
37	  margin: 0;
38	}
39	
40	.flex_between {
41	  display: flex;
42	  align-items: center;
43	  justify-content: space-between;
44	}
45	
46	.list_heading {
47	  font-weight: bold;
48	}
49	
50	.App {
51	  text-align: center;
52	}
53	
54	.container {
55	  width: 100%;
56	  max-width: 500px;
57	  margin: auto;
58	  padding: 20px 0;
59	  text-align: left;
60	}
61	
62	.header {
63	  align-items: center;
64	  padding: 25px 0;
65	  background-color: #208AEC;
66	}
67	
68	.header_logo {
69	  height: 55px;
70	  margin-bottom: 20px;
71	  object-fit: contain;
72	}
73	
74	.header_text_bold {
75	  margin-bottom: 3px;
76	  color: rgba(255, 255, 255, 0.9);
77	  font-size: 16px;
78	  font-weight: bold;
79	}
80	
81	.header_text {
82	  color: rgba(255, 255, 255, 0.9);
83	  font-size: 15px;
84	}
85	
86	.heading {
87	  font-size: 22px;
88	}
89	
90	.form_wrapper {
91	  margin-top: 20px;
92	  margin-bottom: 10px;
93	}
94	
95	.form_input {
96	  margin-bottom: 20px;
97	}
98	
99	.form_button {
100	  width: 100%;
101	}
102	
103	.messages {
104	  margin-top: 25px;
105	}
106	
107	.message_sent {
108	  position: relative;
109	  width: 50%;
110	  margin-left: auto;
111	}
112	
113	.message_received {
114	  position: relative;
115	  width: 50%;
116	}
117	
118	.message_bubble {
119	  padding: 12px;
120	  border-radius: 25px;
121	  background-color: rgba(0, 0, 0, 0.2);
122	}
123	
124	.message_sent .message_bubble {
125	  background-color: #1E88E5;
126	  color: #fff;
127	}
128	
129	.message_time {
130	  position: absolute;
131	  top: 35%;
132	  left: -62px;
133	  font-size: 13px;
134	  color: rgba(0, 0, 0, 0.35);
135	  transform: translateY(-50%);
136	}
137	
138	.message_sent .message_time {
139	  left: initial;
140	  right: -62px;
141	}
142	
143	.message_name {
144	  margin-top: 5px;
145	  color: rgba(0, 0, 0, 0.55);
146	  font-size: 13px;
147	  font-weight: 600;
148	}
149	
150	.message_sent .message_name {
151	  text-align: right;
152	}
153	
154	.new_message {
155	  padding-top: 15px;
156	  margin-top: 20px;
157	  margin-bottom: 10px;
158	  border-top: 1px solid rgba(0, 0, 0, 0.12);
159	}
160	
161	.new_message_title {
162	  margin-bottom: 15px;
163	}
```
:::

## 5 - 测试聊天应用程序

继续测试实时聊天应用程序，通过在你的 <font color="#2166ae">`ChatSetup`</font> 组件上声明和调用 <font color="#2166ae">`App.js`</font> (或 <font color="#2166ae">`App.tsx`</font>) JSX 代码。以下是你可以如何做到的示例:

:::CodeblockTabs
App.js or App.tsx

```javascript
1	import React from "react";
2	import "./App.css";
3	import { initializeParse } from "@parse/react";
4	import { ChatSetup } from "./ChatSetup";
5	
6	// Your Parse initialization configuration goes here
7	// Note the live query URL instead of the regular server url
8	const PARSE_APPLICATION_ID = "YOUR_PARSE_APPLICATION_ID";
9	// const PARSE_SERVER_URL = "https://parseapi.back4app.com/";
10	const PARSE_LIVE_QUERY_URL = "https://YOUR_APP_NAME.b4a.io/";
11	const PARSE_JAVASCRIPT_KEY = "YOUR_PARSE_JAVASCRIPT_KEY";
12	
13	// Initialize parse using @parse/react instead of regular parse JS SDK
14	initializeParse(
15	  PARSE_LIVE_QUERY_URL,
16	  PARSE_APPLICATION_ID,
17	  PARSE_JAVASCRIPT_KEY
18	);
19	
20	function App() {
21	  return (
22	    <div className="App">
23	      <ChatSetup />
24	    </div>
25	  );
26	}
27	
28	export default App;
```
:::

通过在控制台运行 <font color="#2166ae">`yarn start`</font> 启动你的应用程序。现在你应该看到以下屏幕，在其中你需要输入发送和接收的昵称以开始聊天。

::Image[]{src="https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/hNWQU-JPUktv9kZSmyXBX_image.png" size="80" width="658" height="500" position="center" showCaption="false"}

为了更好地查看应用程序和实时查询的工作情况，在两个不同的浏览器窗口中打开相同的应用程序并将它们并排放置。在一个窗口中发送消息后，如果昵称匹配且连接是实时的，你应该会看到它在另一个窗口中弹出。

![](https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/Mm1yxJC_FolmPtYx-kGkB_image.png)

## 结论

在本指南的最后，你学习了如何在一个现实的应用示例中使用 Parse React 钩子进行实时查询。
