ReactJS
Templates
สร้างแอปคล้าย Slack ด้วย React และ Live Query
9 นาที
แอปคล้าย slack ด้วย react บทนำ ในคู่มือนี้ เราจะสำรวจ parse react hook parse react hook ในสถานการณ์ที่มีประโยชน์โดยการสร้างแอปคล้าย slack แอปจะมีฟีเจอร์พื้นฐาน เช่น การลงทะเบียน/เข้าสู่ระบบ ช่อง และการแชทแบบเรียลไทม์ นอกจากนี้ คอมโพเนนต์ react ยังเน้นความมีประโยชน์ของการแจ้งเตือนแบบเรียลไทม์โดยใช้ live query ความสามารถในการจัดการผู้ใช้ และความยืดหยุ่นในการทำการค้นหา (เชิงสัมพันธ์) ในทุกช่วงเวลาคุณสามารถปรับใช้แอปคล้าย slack บน vercel ได้อย่างรวดเร็ว ข้อกำหนดเบื้องต้น ในการทำตามบทเรียนนี้ คุณจะต้องมี บัญชี back4app ฟรี https //www back4app com/signup โคลนหรือดาวน์โหลดโปรเจกต์นี้ผ่าน github ของเราเพื่อให้คุณสามารถรันมันพร้อมกับคู่มือ ตรวจสอบให้แน่ใจว่าคุณได้ทำตามคำแนะนำในไฟล์ readme เพื่อทำการตั้งค่าในสภาพแวดล้อมท้องถิ่นของคุณ https //github com/templates back4app/react js slack clone https //github com/templates back4app/react ts slack clone เป้าหมาย เพื่อสร้างแอปพลิเคชันคล้าย slack บน react โดยใช้ @parse/react @parse/react hook 1 การสร้างแอป parse ของคุณจากเทมเพลต แอปพลิเคชันนี้ประกอบด้วยสองคลาสฐานข้อมูล channel channel และ message message , ซึ่งมีพอยเตอร์ไปยังคลาสผู้ใช้ parse แทนที่จะสร้างแอปและคลาสฐานข้อมูลจากศูนย์ มาคัดลอกเทมเพลตที่มีอยู่ที่ https //www back4app com/database/back4app/slackclone คลิกที่ปุ่ม “clone database” และดำเนินการเข้าสู่ระบบและสร้างแอปของคุณ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการคัดลอกโปรดตรวจสอบที่ https //www back4app com/docs/database hub/clone ตอนนี้แอปของคุณมีโครงสร้างแบ็คเอนด์ที่ครบถ้วนซึ่งจำเป็นในการสร้าง slack clone บน back4app 2 การเปิดใช้งาน live query ตอนนี้ที่คุณได้สร้างแอปและคลาสแล้ว คุณต้องเปิดใช้งานการค้นหาสด (เรียลไทม์) ไปที่แดชบอร์ด back4app ของคุณและไปที่ app settings app settings > server settings server settings > server url and live query server url and live query หลังจากเปิดใช้งานซับโดเมน back4app ของคุณแล้ว คุณสามารถเปิดใช้งาน live query โดยการเลือกคลาส message message และ channel channel หลังจากนั้นให้บันทึกการเปลี่ยนแปลง url ข้างต้นคือ live query url live query url โปรดคัดลอกเพื่อให้แน่ใจว่าคุณได้เริ่มต้น parse hook อย่างถูกต้อง ตอนนี้เรามาดำดิ่งสู่คอมโพเนนต์หลักของ react channellist channellist , messagelist messagelist , และ home home 3 คอมโพเนนต์รายการ ช่องรายการ channellist channellist และ messagelist messagelist ส่วนประกอบใช้ @parse/react @parse/react hook เพื่อดึงข้อมูลผู้ใช้ผ่าน live query พวกเขามีโครงสร้างรูปแบบเดียวกันกับใน https //www back4app com/docs/react/real time/react chat app พวกเขาถูกสร้างขึ้นด้วยพารามิเตอร์เริ่มต้น (ดึงมาจาก props props อ็อบเจ็กต์) ที่ประกอบเป็นคำค้น live query ของพวกเขาแบบไดนามิก ดูที่คำค้นของพวกเขาและวิธีที่คลาสเกี่ยวข้องกับกัน javascript 1 // note that parse object coming from props need to be changed into pointers 2 // to be able to be used by parse, since react changes the object structure 3 // when passing down parameters to child components 4	 5 // channellist js 6	 7 const ownerquery = new parse query("channel"); 8 ownerquery equalto("owner", props currentuser topointer()); 9 const membersquery = new parse query("channel"); 10 membersquery containedin("members", \[props currentuser topointer()]); 11 // creates the or query 12 const parsequery = parse query or(ownerquery, membersquery); 13 // set results ordering 14 parsequery ascending("name"); 15 // include all pointer fields 16 parsequery includeall(); 17	 18 // messagelist js 19	 20 const parsequery = new parse query("message"); 21 // get messages that involve both nicknames 22 parsequery equalto("channel", props currentchannel topointer()); 23 // set results ordering 24 parsequery ascending("createdat"); 25 // include nickname fields, to enable name getting on list 26 parsequery includeall();1 // note that parse object coming from props need to be changed into pointers 2 // to be able to be used by parse, since react changes the object structure 3 // when passing down parameters to child components 4	 5 // channellist tsx 6	 7 // this query is a composite or one, combining the results of both 8 const ownerquery parse query = new parse query("channel"); 9 ownerquery equalto("owner", props currentuser topointer()); 10 const membersquery parse query = new parse query("channel"); 11 membersquery containedin("members", \[props currentuser topointer()]); 12 // creates the or query 13 const parsequery parse query = parse query or(ownerquery, membersquery); 14 // set results ordering 15 parsequery ascending("name"); 16 // include all pointer fields 17 parsequery includeall(); 18	 19 // messagelist tsx 20	 21 const parsequery parse query = new parse query("message"); 22 // get messages that involve both nicknames 23 parsequery equalto("channel", props currentchannel topointer()); 24 // set results ordering 25 parsequery ascending("createdat"); 26 // include nickname fields, to enable name getting on list 27 parsequery includeall(); คำถามเหล่านี้จะทำงานทุกครั้งที่มีการเปลี่ยนแปลงในข้อมูลคลาส ดังนั้นหากผู้ใช้อื่นในช่องส่งข้อความ คุณจะเห็นข้อความนั้นปรากฏที่นั่นแบบเรียลไทม์ 4 ส่วนประกอบหน้าแรก ส่วนประกอบ หน้าแรก หน้าแรก ทำหน้าที่เป็นหน้าจอหลักของแอปพลิเคชัน ซึ่งในนั้นส่วนประกอบรายการจะถูกเรนเดอร์และสร้างขึ้นตามเงื่อนไขเมื่อจำเป็น คุณสามารถดูโค้ดส่วนประกอบด้านล่างนี้ ดูฟังก์ชันสำหรับการสร้างช่องและเชิญผู้ใช้เข้าร่วมช่องเหล่านั้น home js 1 import react, { useeffect, usestate } from "react"; 2 import " /app css"; 3 import { modal } from "antd"; 4 import { usehistory } from "react router dom"; 5 import parse from "parse"; 6 import { channellist } from " /channellist"; 7 import { messagelist } from " /messagelist"; 8 import { memberlist } from " /memberlist"; 9	 10 export const home = () => { 11 const history = usehistory(); 12	 13 // state variables holding input values and flags 14 const \[currentuser, setcurrentuser] = usestate(null); 15 const \[iscreatechannelmodalvisible, setiscreatechannelmodalvisible] = 16 usestate(false); 17 const \[createchannelinput, setcreatechannelinput] = usestate(""); 18 const \[currentchannel, setcurrentchannel] = usestate(null); 19	 20 // this effect hook runs at every render and checks if there is a 21 // logged in user, redirecting to login screen if needed 22 useeffect(() => { 23 const checkcurrentuser = async () => { 24 try { 25 const user = await parse user currentasync(); 26 if (user === null || user === undefined) { 27 history push("/"); 28 } else { 29 if (currentuser === null) { 30 setcurrentuser(user); 31 } 32 } 33 return true; 34 } catch ( error) {} 35 return false; 36 }; 37 checkcurrentuser(); 38 }); 39	 40 // logout function 41 const dologout = async () => { 42 // logout 43 try { 44 await parse user logout(); 45 // force useeffect execution to redirect back to login 46 setcurrentuser(null); 47 return true; 48 } catch (error) { 49 alert(error); 50 return false; 51 } 52 }; 53	 54 // makes modal visible 55 const showcreatechannelmodal = () => { 56 setiscreatechannelmodalvisible(true); 57 }; 58	 59 // clear input and hide modal on cancel 60 const handlecreatechannelmodalcancel = () => { 61 setcreatechannelinput(""); 62 setiscreatechannelmodalvisible(false); 63 }; 64	 65 // creates a channel based on input from modal 66 const docreatechannel = async () => { 67 const channelname = createchannelinput; 68	 69 if (channelname === "") { 70 alert("please inform your new channel name!"); 71 return false; 72 } 73	 74 // creates a new parse object instance and set parameters 75 const channel = new parse object("channel"); 76 channel set("name", channelname); 77 channel set("owner", currentuser); 78 // members is an array of parse user objects, so add() should be used to 79 // concatenate the value inside the array 80 channel add("members", currentuser); 81	 82 // clears input value and hide modal 83 setcreatechannelinput(""); 84 setiscreatechannelmodalvisible(false); 85	 86 try { 87 // save object on parse server 88 const saveresult = await channel save(); 89 // set the created channel as the active channel, 90 // showing the message list for this channel 91 setcurrentchannel(saveresult); 92 alert(`success on creating channel ${channelname}`); 93 return true; 94 } catch (error) { 95 alert(error); 96 return false; 97 } 98 }; 99	 100 // changes the active channel and shows the message list for it 101 // this is called using a callback in the channellist component 102 const doselectchannel = (channel) => { 103 setcurrentchannel(null); 104 setcurrentchannel(channel); 105 }; 106	 107 // settings current channel to null hides the message list component 108 // this is called using a callback in the messagelist component 109 const doclearcurrentchannel = () => { 110 setcurrentchannel(null); 111 }; 112	 113 return ( 114 \<div classname="grid"> 115 \<div classname="organizations"> 116 \<div classname="organization"> 117 \<picture classname="organization picture"> 118 \<img 119 classname="organization img" 120 src="https //scontent fsqx1 1 fna fbcdn net/v/t1 6435 9/29136314 969639596535770 8356900498426560512 n png? nc cat=103\&ccb=1 5& nc sid=973b4a& nc ohc=d9actpsb8duax zaa7f& nc ht=scontent fsqx1 1 fna\&oh=96679a09c5c4524f0a6c86110de697b6\&oe=618525f9" 121 alt="" 122 /> 123 \</picture> 124 \<p classname="organization title">back4app\</p> 125 \</div> 126 \<button classname="button inline" onclick={dologout}> 127 \<svg 128 classname="button inline icon" 129 xmlns="http //www w3 org/2000/svg" 130 width="24" 131 height="24" 132 viewbox="0 0 24 24" 133 fill="none" 134 stroke="currentcolor" 135 strokewidth="2" 136 strokelinecap="round" 137 strokelinejoin="round" 138 > 139 \<polyline points="9 10 4 15 9 20">\</polyline> 140 \<path d="m20 4v7a4 4 0 0 1 4 4h4">\</path> 141 \</svg> 142 \<span classname="button inline label">log out\</span> 143 \</button> 144 \</div> 145 \<div classname="channels"> 146 {/ action buttons (new channel and logout) /} 147 \<div> 148 \<modal 149 title="create new channel" 150 visible={iscreatechannelmodalvisible} 151 onok={docreatechannel} 152 oncancel={handlecreatechannelmodalcancel} 153 oktext={"create"} 154 > 155 <> 156 \<label>{"channel name"}\</label> 157 \<input 158 type={"text"} 159 value={createchannelinput} 160 placeholder={"new channel name"} 161 onchange={(event) => setcreatechannelinput(event target value)} 162 >\</input> 163 \</> 164 \</modal> 165 \</div> 166 \<div classname="channels header" onclick={showcreatechannelmodal}> 167 \<p classname="channels header label">channels\</p> 168 \<svg 169 classname="channels header icon" 170 xmlns="http //www w3 org/2000/svg" 171 height="24px" 172 viewbox="0 0 24 24" 173 width="24px" 174 fill="#000000" 175 > 176 \<path d="m0 0h24v24h0z" fill="none" /> 177 \<path d="m19 13h 6v6h 2v 6h5v 2h6v5h2v6h6v2z" /> 178 \</svg> 179 \</div> 180 {/ channel list component, instantiated only when the user is successfully fetched /} 181 {currentuser !== null && ( 182 \<channellist 183 currentuser={currentuser} 184 selectchannelcallback={doselectchannel} 185 /> 186 )} 187 \</div> 188 \<div classname="messages"> 189 {/ message list component, instantiated only when there is a selected channel /} 190 {currentuser !== null && currentchannel !== null && ( 191 \<messagelist 192 currentuser={currentuser} 193 currentchannel={currentchannel} 194 closechannelcallback={doclearcurrentchannel} 195 /> 196 )} 197 \</div> 198 \<div classname="info"> 199 {/ member list component, instantiated only when there is a selected channel /} 200 {currentuser !== null && currentchannel !== null && ( 201 \<memberlist 202 currentuser={currentuser} 203 currentchannel={currentchannel} 204 closechannelcallback={doclearcurrentchannel} 205 /> 206 )} 207 \</div> 208 \</div> 209 ); 210 }; home tsx 1 import react, { useeffect, usestate, fc, reactelement } from 'react'; 2 import ' /app css'; 3 import { modal } from 'antd'; 4 import { usehistory } from 'react router dom'; 5 import parse from 'parse'; 6 import { channellist } from ' /channellist'; 7 import { messagelist } from ' /messagelist'; 8 import { memberlist } from ' /memberlist'; 9	 10 export const home fc<{}> = () reactelement => { 11 const history = usehistory(); 12	 13 // state variables holding input values and flags 14 const \[currentuser, setcurrentuser] = usestate\<parse user | null>(null); 15 const \[iscreatechannelmodalvisible, setiscreatechannelmodalvisible] = usestate(false); 16 const \[createchannelinput, setcreatechannelinput] = usestate(''); 17 const \[currentchannel, setcurrentchannel] = usestate\<parse object | null>(null); 18	 19 // this effect hook runs at every render and checks if there is a 20 // logged in user, redirecting to login screen if needed 21 useeffect(() => { 22 const checkcurrentuser = async () promise\<boolean> => { 23 try { 24 const user (parse user | null) = await parse user currentasync(); 25 if (user === null || user === undefined) { 26 history push('/'); 27 } else { 28 if (currentuser === null) { 29 setcurrentuser(user); 30 } 31 } 32 return true; 33 } catch ( error any) {} 34 return false; 35 } 36 checkcurrentuser(); 37 }); 38	 39 // logout function 40 const dologout = async () promise\<boolean> => { 41 // logout 42 try { 43 await parse user logout(); 44 // force useeffect execution to redirect back to login 45 setcurrentuser(null); 46 return true; 47 } catch (error any) { 48 alert(error); 49 return false; 50 } 51 }; 52	 53 // makes modal visible 54 const showcreatechannelmodal = () void => { 55 setiscreatechannelmodalvisible(true); 56 } 57	 58 // clear input and hide modal on cancel 59 const handlecreatechannelmodalcancel = () void => { 60 setcreatechannelinput(""); 61 setiscreatechannelmodalvisible(false); 62 } 63	 64 // creates a channel based on input from modal 65 const docreatechannel = async () promise\<boolean> => { 66 const channelname string = createchannelinput; 67 68 if (channelname === '') { 69 alert("please inform your new channel name!"); 70 return false; 71 } 72	 73 // creates a new parse object instance and set parameters 74 const channel parse object = new parse object("channel"); 75 channel set('name', channelname); 76 channel set('owner', currentuser); 77 // members is an array of parse user objects, so add() should be used to 78 // concatenate the value inside the array 79 channel add('members', currentuser); 80	 81 // clears input value and hide modal 82 setcreatechannelinput(""); 83 setiscreatechannelmodalvisible(false); 84	 85 try { 86 // save object on parse server 87 const saveresult parse object = await channel save(); 88 // set the created channel as the active channel, 89 // showing the message list for this channel 90 setcurrentchannel(saveresult); 91 alert(`success on creating channel ${channelname}`); 92 return true; 93 } catch (error any) { 94 alert(error); 95 return false; 96 } 97 } 98	 99 // changes the active channel and shows the message list for it 100 // this is called using a callback in the channellist component 101 const doselectchannel = (channel parse object) void => { 102 setcurrentchannel(null); 103 setcurrentchannel(channel); 104 } 105	 106 // settings current channel to null hides the message list component 107 // this is called using a callback in the messagelist component 108 const doclearcurrentchannel = () void => { 109 setcurrentchannel(null); 110 } 111	 112 return ( 113 \<div classname="grid"> 114 \<div classname="organizations"> 115 \<div classname="organization"> 116 \<picture classname="organization picture"> 117 \<img classname="organization img" src="https //scontent fsqx1 1 fna fbcdn net/v/t1 6435 9/29136314 969639596535770 8356900498426560512 n png? nc cat=103\&ccb=1 5& nc sid=973b4a& nc ohc=d9actpsb8duax zaa7f& nc ht=scontent fsqx1 1 fna\&oh=96679a09c5c4524f0a6c86110de697b6\&oe=618525f9" alt="" /> 118 \</picture> 119 \<p classname="organization title">back4app\</p> 120 \</div> 121 \<button classname="button inline" onclick={dologout}> 122 \<svg classname="button inline icon" xmlns="http //www w3 org/2000/svg" width="24" height="24" viewbox="0 0 24 24" fill="none" stroke="currentcolor" strokewidth="2" strokelinecap="round" strokelinejoin="round">\<polyline points="9 10 4 15 9 20">\</polyline>\<path d="m20 4v7a4 4 0 0 1 4 4h4">\</path>\</svg> 123 \<span classname="button inline label">log out\</span> 124 \</button> 125 \</div> 126 \<div classname="channels"> 127 {/ action buttons (new channel and logout) /} 128 \<div> 129 \<modal 130 title="create new channel" 131 visible={iscreatechannelmodalvisible} 132 onok={docreatechannel} 133 oncancel={handlecreatechannelmodalcancel} 134 oktext={'create'} 135 > 136 <> 137 \<label>{'channel name'}\</label> 138 \<input 139 type={"text"} 140 value={createchannelinput} 141 placeholder={"new channel name"} 142 onchange={(event) => setcreatechannelinput(event target value)} 143 >\</input> 144 \</> 145 \</modal> 146 \</div> 147 \<div classname="channels header" onclick={showcreatechannelmodal}> 148 \<p classname="channels header label">channels\</p> 149 \<svg classname="channels header icon" xmlns="http //www w3 org/2000/svg" height="24px" viewbox="0 0 24 24" width="24px" fill="#000000">\<path d="m0 0h24v24h0z" fill="none"/>\<path d="m19 13h 6v6h 2v 6h5v 2h6v5h2v6h6v2z"/>\</svg> 150 \</div> 151 {/ channel list component, instantiated only when the user is successfully fetched /} 152 {currentuser !== null && ( 153 \<channellist 154 currentuser={currentuser} 155 selectchannelcallback={doselectchannel} 156 /> 157 )} 158 \</div> 159 \<div classname="messages"> 160 {/ message list component, instantiated only when there is a selected channel /} 161 {currentuser !== null && currentchannel !== null && ( 162 \<messagelist 163 currentuser={currentuser} 164 currentchannel={currentchannel} 165 closechannelcallback={doclearcurrentchannel} 166 /> 167 )} 168 \</div> 169 \<div classname="info"> 170 {/ member list component, instantiated only when there is a selected channel /} 171 {currentuser !== null && currentchannel !== null && ( 172 \<memberlist 173 currentuser={currentuser} 174 currentchannel={currentchannel} 175 closechannelcallback={doclearcurrentchannel} 176 /> 177 )} 178 \</div> 179 \</div> 180 ); 181 }; วิธีการนี้ในการสร้างส่วนประกอบ live query แบบไดนามิกช่วยให้เราสามารถนำกลับมาใช้ใหม่ได้เมื่อใดก็ตามที่ผู้ใช้เปลี่ยนช่องที่ใช้งานอยู่ สร้างช่องใหม่ ส่งข้อความ ฯลฯ นี่คือวิธีที่แอปพลิเคชันทั้งหมดจะมีลักษณะ 5 ติดตั้งบน vercel คุณสามารถติดตั้งแอปพลิเคชันบน vercel ได้ทุกเมื่อโดยการคลิกที่ลิงก์ด้านล่าง ตรวจสอบให้แน่ใจว่าคุณมี application id application id , client key client key และ livequery url livequery url สำหรับคีย์คุณสามารถไปที่ app settings app settings > security & keys security & keys และจากนั้นคัดลอก สำหรับ live query url live query url คุณสามารถไปที่ขั้นตอนที่ 2 และคัดลอกมัน บทสรุป ในตอนท้ายของคู่มือนี้ คุณได้เรียนรู้เพิ่มเติมเกี่ยวกับการใช้ parse react hook สำหรับการค้นหาสดใน parse และวิธีการใช้ database hub ของ back4app