리액트 실시간 채팅 앱 개발 및 Parse React 훅 활용
리액트 채팅 앱 - 실시간
소개
지난 가이드에서는 @parse/react 헬퍼 라이브러리에 대해 더 알게 되었습니다. 이 라이브러리는 리액트 애플리케이션에서 Parse Live Query 지원을 빠르게 활성화합니다. 이 라이브러리는 전적으로 Typescript로 작성되었으며, Parse Javascript SDK, 현재 알파 버전입니다.
이제 이 가이드에서는 Parse React 훅을 사용하여 간단한 실시간 채팅 애플리케이션을 만드는 현실적인 상황을 다룰 것입니다. 이 앱은 Parse의 Live Query의 유용성을 강조하는 두 개의 리액트 컴포넌트로 구성되어 있으며, 완전한 실시간 앱을 만드는 데 필요한 모든 것을 보여줍니다.
Parse React Native는 현재 알파 버전입니다. 이 라이브러리는 테스트 중이므로 주의해서 진행하는 것이 좋습니다. 여러분의 피드백은 매우 감사하므로, 이 라이브러리를 사용하고 질문이나 첫 인상을 [email protected]으로 이메일로 보내주시면 됩니다.
전제 조건
이 튜토리얼을 완료하려면 다음이 필요합니다:
- 이 가이드에서 제공하는 화면 레이아웃을 테스트/사용하려면 Ant Design 라이브러리를 설정해야 합니다.
목표
리액트에서 @parse/react 훅을 사용하여 실시간 채팅 애플리케이션을 구축하는 것입니다.
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 서브도메인을 활성화한 후, 실시간 쿼리를 활성화하고 어떤 DB 클래스를 활성화할지 선택할 수 있습니다. 새 클래스를 선택하고 변경 사항을 저장하는 것을 잊지 마세요.

다음으로 할 일은 두 개의 구성 요소로 구성된 채팅 앱을 만드는 것입니다. ChatSetup 과 LiveChat입니다.
3 - 채팅 설정 구성 요소 만들기
이 구성 요소는 발신자와 수신자 Nickname 객체를 생성하고 설정하는 역할을 하며, 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에서 Live Query를 설정하여 이 채팅 인스턴스와 관련된 메시지 객체를 검색합니다. 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 훅 설정이 있습니다. 여기에서 props 매개변수가 쿼리가 우리가 원하는 메시지를 검색할 수 있도록 하는 방법을 볼 수 있습니다;
- 그 후에 sendMessage 함수가 있으며, 이는 새로운 Message 객체를 생성하여 이 채팅 인스턴스에서 사용되는 Nickname 과 관련이 있습니다. 메시지 날짜 값을 포맷하는 도우미 함수도 있습니다;
- 이제 JSX 코드 안에는 Parse React 훅 변수와 관련된 상태 플래그와 연결 재로드 버튼이 있습니다;
- 마지막으로, 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의 실시간 쿼리를 위한 Parse React 훅을 사용하는 방법을 배웠습니다.