使用Parse React Hook构建实时聊天应用程序示例
React 聊天应用 - 实时
介绍
在上一个指南中,您了解了更多关于 @parse/react 助手库,该库可以快速在您的 React 应用程序中启用 Parse 实时查询支持。该库完全用 Typescript 编写,基于 Parse Javascript SDK, 目前处于 Alpha 版本。
现在,在本指南中,您将使用 Parse React 钩子在创建一个简单的实时聊天应用程序的实际情况下。该应用程序由两个 React 组件组成,突出了 Parse 的实时查询的实用性,并展示了您创建完整实时应用所需了解的一切。
Parse React Native 目前处于 Alpha 版本。该库正在测试中,因此我们建议谨慎使用。我们非常感谢您的反馈,因此请随时使用该库,并通过发送电子邮件至 [email protected] 向我们提出您的问题和初步印象。
先决条件
要完成本教程,您需要:
目标
使用 @parse/react 钩子作为 Parse 上实时查询的启用器,构建一个实时聊天应用程序。
1 - 理解和创建数据库类
聊天应用程序将由两个小数据库类组成: 昵称 和 消息. 昵称 只有一个名为 名称 的文本字段,并将代表应用程序中的用户。 消息 将保存两个用户之间发送的任何文本消息,因此它需要有一个名为 文本 的文本字段和两个对象指针字段,分别称为 发送者 和 接收者, 两者都与 昵称 类相关。
在您的 Back4App 仪表板的 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 - 启用实时查询
现在您已经创建了 昵称 和 消息 类,我们需要为它们启用实时查询功能。请转到您的 Back4App 仪表板并导航到 应用设置 > 服务器设置 > 服务器 URL 和实时查询. 在激活您的 Back4App 子域后,您可以激活实时查询并选择哪些数据库类将启用它。确保选择新类并保存更改。

接下来要做的是创建我们的聊天应用程序,它由两个组件组成, ChatSetup 和 LiveChat.
3 - 创建聊天设置组件
这个组件负责创建和设置发送者和接收者 昵称 对象,同时也作为 LiveChat 组件结构的容器。设置部分的布局只有两个用于昵称的输入字段和一个触发设置功能的按钮。在你的 src 目录中创建一个名为 ChatSetup.js (或 ChatSetup.tsx) 并使用以下代码:
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 };请注意,LiveChat 组件仅在设置过程成功且所有状态变量正确设置时初始化和渲染。同样,设置输入在过程结束后被隐藏,子组件布局被渲染。
4 - 创建实时聊天组件
该 LiveChat 组件处理展示和发送 消息 在两个 昵称 作为参数传递给它的初始化。正是在这个组件中,您将最终使用 useParseQuery 钩子来自 @parse/react 来设置将检索与此聊天实例相关的任何 消息 对象的实时查询。在您的 src 目录中创建一个名为 LiveChat.js (或 LiveChat.tsx) 的新文件,并插入以下代码。
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 };让我们将这个组件结构分解为四个部分,以便您更好地理解其布局:
- 在顶部,我们有消息的 Parse.Query 和 Parse React hook 设置。在这里,您可以看到 props 参数是如何被用来使查询能够检索我们想要的消息;
- 之后,您有 sendMessage 函数,它将创建一个新的 Message 对象,将其与此聊天实例中使用的 Nickname 关联。还有一个帮助函数用于格式化消息日期值;
- 现在,在 JSX 代码中,我们有与 Parse React hook 变量相关的状态标志,以及连接重载按钮;
- 最后,您可以看到 Message 列表,其中渲染的列表项样式由其发送者值决定。在底部,我们有消息发送部分,包含一个简单的文本输入和一个按钮。
最后,如果您想完全渲染组件布局,请将这些类添加到您的 App.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 - 测试聊天应用程序
继续测试实时聊天应用程序,通过在你的 ChatSetup 组件上声明和调用 App.js (或 App.tsx) JSX 代码。以下是你可以如何做到的示例:
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;通过在控制台运行 yarn start 启动你的应用程序。现在你应该看到以下屏幕,在其中你需要输入发送和接收的昵称以开始聊天。

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

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