Project Templates
Social Network
Feed und Interaktionen für Ihr soziales Netzwerk
39 min
einführung in diesem tutorial lernen sie, wie sie einen sozialen netzwerk feed mit interaktiven funktionen wie beiträgen, kommentaren und likes erstellen, wobei sie back4app als ihren backend service verwenden diese funktionen bilden den kern jeder social networking anwendung und ermöglichen es den benutzern, inhalte zu teilen und mit anderen benutzern zu interagieren back4app ist eine backend as a service (baas) plattform, die auf parse server basiert und entwicklern eine robuste infrastruktur für die erstellung skalierbarer anwendungen bietet, ohne serverseitigen code verwalten zu müssen die echtzeit datenbankfunktionen und das integrierte benutzermanagement machen es zu einer ausgezeichneten wahl für die entwicklung von social networking anwendungen am ende dieses tutorials haben sie einen voll funktionsfähigen sozialen feed implementiert, der es benutzern ermöglicht beiträge mit text und bildern erstellen beiträge von anderen benutzern in einem feed anzeigen beiträge liken kommentare zu beiträgen abgeben beitragsdetails mit kommentaren anzeigen sie lernen, wie sie ihre datenbankklassen effizient strukturieren, echtzeit updates implementieren und eine responsive benutzeroberfläche erstellen, die ein nahtloses erlebnis auf verschiedenen geräten bietet voraussetzungen um dieses tutorial abzuschließen, benötigen sie ein back4app konto sie können sich für ein kostenloses konto anmelden unter back4app com https //www back4app com ein back4app projekt, das eingerichtet ist sie können lernen, wie man ein neues projekt erstellt, indem sie unseren leitfaden zum einstieg in back4app https //www back4app com/docs/get started/welcome node js auf ihrem lokalen rechner installiert grundkenntnisse in javascript und react js ein funktionierendes authentifizierungssystem wenn sie noch keines eingerichtet haben, folgen sie zuerst unserem tutorial zum authentifizierungssystem für soziale netzwerke tutorial vertrautheit mit modernen webentwicklungskonzepten (komponenten, zustandsverwaltung usw ) back4gram projekt finden sie hier den vollständigen code für ein beispielprojekt für soziale netzwerke erstellt mit back4app schritt 1 – verständnis des datenmodells bevor sie mit dem schreiben von code beginnen, ist es wichtig, das datenmodell für unser soziales netzwerk zu verstehen wir benötigen mehrere parse klassen, um die feed funktionalität zu erstellen beitragsklasse die beitrags klasse speichert alle beitragsbezogenen informationen autor ein zeiger auf den benutzer, der den beitrag erstellt hat inhalt der textinhalt des beitrags bild eine optionale bilddatei likes eine zahl, die angibt, wie viele likes der beitrag hat kommentare ein array von zeigern auf kommentarobjekte (optional, kann separat abgefragt werden) erstelltam automatisch von back4app verwaltet aktualisiertam automatisch von back4app verwaltet kommentar klasse der kommentar klasse speichert kommentare zu beiträgen autor ein verweis auf den benutzer, der den kommentar geschrieben hat beitrag ein verweis auf den beitrag, zu dem kommentiert wird inhalt der textinhalt des kommentars erstelltam automatisch verwaltet von back4app aktualisiertam automatisch verwaltet von back4app gefällt mir verfolgung wir haben zwei optionen zur verfolgung von likes einfacher zähler speichern sie eine anzahl von likes für jeden beitrag (einfacher, aber weniger detailliert) gefällt mir aufzeichnungen erstellen sie eine separate gefällt mir klasse, die jedes einzelne like verfolgt (detaillierter) für dieses tutorial werden wir beide ansätze implementieren, beginnend mit dem einfacheren zähler und dann zeigen, wie man individuelle like aufzeichnungen für fortgeschrittene funktionen implementiert lass uns damit beginnen, diese klassen in back4app einzurichten schritt 2 – einrichten des datenmodells in back4app in diesem schritt erstellen sie die erforderlichen klassen in ihrer back4app datenbank erstellen der post klasse melden sie sich bei ihrem back4app dashboard an und navigieren sie zu ihrem projekt klicken sie in der linken seitenleiste auf "datenbank", um den datenbankbrowser zu öffnen klicken sie auf die schaltfläche "eine klasse erstellen" oben auf der seite geben sie im erscheinenden modal "post" als klassennamen ein und wählen sie "benutzerdefiniert" als typ aus klicken sie dann auf "klasse erstellen" \[bild back4app erstellen klasse modal mit "post" als klassennamen eingegeben] fügen sie nun die folgenden spalten zu ihrer post klasse hinzu klicken sie auf "spalte hinzufügen" erstellen sie die folgenden spalten autor (typ zeiger, zielklasse user) inhalt (typ string) bild (typ datei) likes (typ zahl, standardwert 0) erstellen der kommentar klasse klicken sie erneut auf "eine klasse erstellen" geben sie "kommentar" als klassennamen ein und wählen sie "benutzerdefiniert" als typ aus fügen sie die folgenden spalten hinzu autor (typ zeiger, zielklasse user) post (typ zeiger, zielklasse post) inhalt (typ string) einrichten von klassenberechtigungen zur sicherheit lassen sie uns geeignete berechtigungen für unsere klassen einrichten wählen sie im datenbankbrowser die klasse post aus klicken sie auf die schaltfläche "sicherheit" berechtigungen konfigurieren öffentliche leseberechtigung ja (jeder kann beiträge ansehen) öffentliche schreibberechtigung nein (nur authentifizierte benutzer können beiträge erstellen) öffentliches feld hinzufügen nein suchen, abrufen, erstellen, aktualisieren, löschen benutzerautorisierung erforderlich wiederholen sie ähnliche einstellungen für die kommentar klasse jetzt, da wir unser datenmodell eingerichtet haben, lassen sie uns die front end komponenten für unseren sozialen netzwerk feed implementieren die feed seite ist das herzstück jedes sozialen netzwerks sie zeigt beiträge von nutzern an und ermöglicht interaktionen lassen sie uns eine feed komponente erstellen, die beiträge abruft und anzeigt erstellen sie eine datei mit dem namen src/pages/feedpage js import react, { usestate, useeffect, useref } from 'react'; import { usenavigate } from 'react router dom'; import { box, flex, vstack, heading, text, button, input, simplegrid, avatar, hstack, iconbutton, textarea, spinner, center, image, } from '@chakra ui/react'; import { link as routerlink } from 'react router dom'; import parse from 'parse/dist/parse min js'; import { toaster } from ' /components/ui/toaster'; function feedpage() { const \[searchquery, setsearchquery] = usestate(''); const \[newpostcontent, setnewpostcontent] = usestate(''); const \[posts, setposts] = usestate(\[]); const \[isloading, setisloading] = usestate(true); const \[isposting, setisposting] = usestate(false); const \[currentuser, setcurrentuser] = usestate(null); // new state variables for image upload const \[selectedimage, setselectedimage] = usestate(null); const \[imagepreview, setimagepreview] = usestate(null); const fileinputref = useref(null); const navigate = usenavigate(); // check if user is authenticated useeffect(() => { const checkauth = async () => { try { const user = await parse user current(); if (!user) { // redirect to login if not authenticated navigate('/login'); return; } setcurrentuser(user); } catch (error) { console error('error checking authentication ', error); navigate('/login'); } }; checkauth(); }, \[navigate]); // handle image selection const handleimagechange = (e) => { if (e target files && e target files\[0]) { const file = e target files\[0]; setselectedimage(file); // create a preview url const reader = new filereader(); reader onloadend = () => { setimagepreview(reader result); }; reader readasdataurl(file); } }; // clear selected image const handleclearimage = () => { setselectedimage(null); setimagepreview(null); if (fileinputref current) { fileinputref current value = ''; } }; // fetch posts useeffect(() => { const fetchposts = async () => { if (!currentuser) { console log('no current user, skipping post fetch'); return; } setisloading(true); console log('fetching posts for user ', currentuser id); try { // create a query for the post class const query = new parse query('post'); console log('created post query'); // include the user who created the post query include('author'); console log('including author in query'); // sort by creation date, newest first query descending('createdat'); // limit to 20 posts query limit(20); // execute the query console log('executing query '); const results = await query find(); console log('query results received ', results length, 'posts found'); // convert parse objects to plain objects const fetchedposts = \[]; for (let i = 0; i < results length; i++) { const post = results\[i]; try { const author = post get('author'); console log(`processing post ${i+1}/${results length}, author `, author ? author id 'null'); if (!author) { console warn(`post ${post id} has no author, skipping`); continue; } const postobj = { id post id, content post get('content'), author { id author id, username author get('username'), avatar author get('avatar') ? author get('avatar') url() null }, image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat') }; fetchedposts push(postobj); } catch (posterror) { console error(`error processing post ${i+1} `, posterror); console error('post data ', post tojson()); } } console log('successfully processed', fetchedposts length, 'posts'); setposts(fetchedposts); } catch (error) { console error('error fetching posts ', error); console error('error details ', error code, error message); toaster create({ title 'error loading posts', description error message, type 'error', }); } finally { setisloading(false); console log('post loading completed'); } }; fetchposts(); }, \[currentuser, navigate]); // function to create a new post with image const handlecreatepost = async () => { if (!newpostcontent trim() && !selectedimage) return; setisposting(true); console log('creating new post '); try { // create a new post object const post = parse object extend('post'); const post = new post(); // set post data post set('content', newpostcontent); post set('author', currentuser); post set('likes', 0); // handle image upload if an image is selected if (selectedimage) { console log('uploading image '); const parsefile = new parse file(selectedimage name, selectedimage); await parsefile save(); post set('image', parsefile); console log('image uploaded successfully'); } console log('post object created, saving to database '); // save the post const savedpost = await post save(); console log('post saved successfully with id ', savedpost id); // add the new post to the state const newpost = { id savedpost id, content savedpost get('content'), author { id currentuser id, username currentuser get('username'), avatar currentuser get('avatar') ? currentuser get('avatar') url() null }, image savedpost get('image') ? savedpost get('image') url() null, likes 0, createdat savedpost get('createdat') }; console log('adding new post to state'); setposts(\[newpost, posts]); setnewpostcontent(''); setselectedimage(null); setimagepreview(null); toaster create({ title 'post created', description 'your post has been published successfully!', type 'success', }); } catch (error) { console error('error creating post ', error); console error('error details ', error code, error message); toaster create({ title 'error creating post', description error message, type 'error', }); } finally { setisposting(false); console log('post creation completed'); } }; // function to like a post const handlelikepost = async (postid) => { try { // get the post const query = new parse query('post'); const post = await query get(postid); // increment likes post increment('likes'); await post save(); // update the post in the state setposts(posts map(p => { if (p id === postid) { return { p, likes p likes + 1 }; } return p; })); } catch (error) { console error('error liking post ', error); toaster create({ title 'error', description 'could not like the post please try again ', type 'error', }); } }; // function to logout const handlelogout = async () => { try { await parse user logout(); navigate('/login'); } catch (error) { console error('error logging out ', error); } }; const handlesearchsubmit = (e) => { e preventdefault(); if (searchquery trim()) { navigate(`/search?q=${encodeuricomponent(searchquery)}`); } }; // format date const formatdate = (date) => { return new date(date) tolocalestring(); }; return ( \<flex direction="row" h="100vh"> {/ left sidebar (navigation) /} \<box w={\['0px', '250px']} bg="gray 700" p={4} display={\['none', 'block']} borderright="1px solid" bordercolor="gray 600" \> \<vstack align="stretch" spacing={4}> \<heading size="md">social network\</heading> \<button as={routerlink} to="/feed" variant="ghost" justifycontent="flex start"> home \</button> \<button as={routerlink} to="/search" variant="ghost" justifycontent="flex start"> search \</button> \<button as={routerlink} to="/messages" variant="ghost" justifycontent="flex start"> messages \</button> \<button as={routerlink} to="/profile" variant="ghost" justifycontent="flex start"> profile \</button> \<button onclick={handlelogout} variant="ghost" colorscheme="red" justifycontent="flex start"> logout \</button> \</vstack> \</box> {/ main content (feed) /} \<box flex="1" p={4} overflowy="auto"> \<form onsubmit={handlesearchsubmit}> \<input placeholder="search " value={searchquery} onchange={(e) => setsearchquery(e target value)} mb={4} /> \</form> {/ create post with image upload /} \<box border="1px solid" bordercolor="gray 600" p={4} borderradius="md" mb={6}> \<textarea placeholder="what's on your mind?" value={newpostcontent} onchange={(e) => setnewpostcontent(e target value)} mb={2} resize="none" /> {/ image preview /} {imagepreview && ( \<box position="relative" mb={2}> \<image src={imagepreview} alt="preview" maxh="200px" borderradius="md" /> \<button position="absolute" top="2" right="2" size="sm" colorscheme="red" onclick={handleclearimage} \> remove \</button> \</box> )} \<box> \<input type="file" accept="image/ " onchange={handleimagechange} style={{ display 'none' }} ref={fileinputref} id="image upload" /> \<button as="label" htmlfor="image upload" cursor="pointer" variant="outline" size="sm" mr={2} mb={0} \> 📷 add photo \</button> \</box> \<button colorscheme="blue" onclick={handlecreatepost} isloading={isposting} disabled={(!newpostcontent trim() && !selectedimage) || isposting} \> post \</button> \</box> {/ posts feed with images /} {isloading ? ( \<center py={10}> \<spinner size="xl" /> \</center> ) posts length > 0 ? ( \<vstack align="stretch" spacing={4}> {posts map(post => ( \<box key={post id} border="1px solid" bordercolor="gray 600" p={4} borderradius="md" \> \<hstack mb={2}> \<avatar src={post author avatar} name={post author username} size="sm" /> \<text fontweight="bold">{post author username}\</text> \<text fontsize="sm" color="gray 400">• {formatdate(post createdat)}\</text> \</hstack> \<text mb={post image ? 2 4}>{post content}\</text> {/ display post image if available /} {post image && ( \<box mb={4}> \<image src={post image} alt="post image" borderradius="md" maxh="400px" w="auto" /> \</box> )} \<hstack spacing={4}> \<button variant="ghost" size="sm" onclick={() => handlelikepost(post id)} \> ❤️ {post likes} \</button> \<button variant="ghost" size="sm" as={routerlink} to={`/post/${post id}`} \> 💬 comment \</button> \<button variant="ghost" size="sm"> 🔄 share \</button> \</hstack> \</box> ))} \</vstack> ) ( \<text>no posts yet follow users or create your first post!\</text> )} \</box> {/ right sidebar (trending hashtags) /} \<box w={\['0px', '250px']} bg="gray 700" p={4} display={\['none', 'block']} borderleft="1px solid" bordercolor="gray 600" \> \<heading size="md" mb={4}> trending today \</heading> \<simplegrid columns={1} spacing={2}> \<button variant="outline" size="sm" colorscheme="whitealpha">#travel\</button> \<button variant="outline" size="sm" colorscheme="whitealpha">#tech\</button> \<button variant="outline" size="sm" colorscheme="whitealpha">#foodie\</button> \</simplegrid> \</box> \</flex> ); } export default feedpage; diese feed komponente umfasst mehrere wichtige funktionen authentifizierungsprüfung stellt sicher, dass nur angemeldete benutzer den feed anzeigen können beitragserstellung ermöglicht benutzern, neue beiträge mit text und optionalen bildern zu erstellen bild upload verarbeitet die bildauswahl, vorschau und den upload mit parse file feed anzeige zeigt beiträge von allen benutzern in umgekehrter chronologischer reihenfolge an gefällt mir funktionalität ermöglicht benutzern, beiträge mit einem einfachen zähleransatz zu liken navigation bietet links zu anderen teilen der anwendung lassen sie uns untersuchen, wie back4app uns hilft, diese funktionen zu implementieren beitragserstellung mit parse back4app macht die erstellung von beiträgen mit bildern einfach // create a new post object const post = parse object extend('post'); const post = new post(); // set post data post set('content', newpostcontent); post set('author', currentuser); post set('likes', 0); // handle image upload if an image is selected if (selectedimage) { const parsefile = new parse file(selectedimage name, selectedimage); await parsefile save(); post set('image', parsefile); } // save the post const savedpost = await post save(); parse file verarbeitet automatisch datei uploads, speicherung und url generierung, was es einfach macht, bilder zu beiträgen hinzuzufügen beiträge mit parse query abrufen das abfragesystem von back4app erleichtert das abrufen und anzeigen von beiträgen // create a query for the post class const query = new parse query('post'); // include the user who created the post query include('author'); // sort by creation date, newest first query descending('createdat'); // limit to 20 posts query limit(20); // execute the query const results = await query find(); die include('author') methode ist besonders leistungsstark, da sie automatisch das referenzierte benutzerobjekt mit jedem beitrag einfügt, wodurch die notwendigkeit für mehrere abfragen verringert wird back4gram projekt finde hier den vollständigen code für ein beispielprojekt für ein soziales netzwerk erstellt mit back4app schritt 4 – implementierung der detailansicht eines beitrags mit kommentaren jetzt erstellen wir eine detailseite für einen beitrag, die einen einzelnen beitrag mit seinen kommentaren anzeigt und es den benutzern ermöglicht, neue kommentare hinzuzufügen erstellen sie eine datei mit dem namen src/pages/postdetailspage js import react, { usestate, useeffect } from 'react'; import { useparams, usenavigate, link as routerlink } from 'react router dom'; import { box, flex, vstack, heading, text, button, avatar, hstack, textarea, spinner, center, image } from '@chakra ui/react'; import parse from 'parse/dist/parse min js'; import { toaster } from ' /components/ui/toaster'; function postdetailspage() { const { id } = useparams(); const navigate = usenavigate(); const \[post, setpost] = usestate(null); const \[comments, setcomments] = usestate(\[]); const \[newcomment, setnewcomment] = usestate(''); const \[isloading, setisloading] = usestate(true); const \[iscommenting, setiscommenting] = usestate(false); const \[currentuser, setcurrentuser] = usestate(null); // check if user is authenticated useeffect(() => { const checkauth = async () => { try { const user = await parse user current(); if (!user) { navigate('/login'); return; } setcurrentuser(user); } catch (error) { console error('error checking authentication ', error); navigate('/login'); } }; checkauth(); }, \[navigate]); // fetch post and comments useeffect(() => { const fetchpostdetails = async () => { if (!currentuser) return; setisloading(true); try { // get the post const query = new parse query('post'); query include('author'); const postobject = await query get(id); // get comments const commentquery = new parse query('comment'); commentquery equalto('post', postobject); commentquery include('author'); commentquery ascending('createdat'); const commentresults = await commentquery find(); // convert post to plain object const fetchedpost = { id postobject id, content postobject get('content'), author { id postobject get('author') id, username postobject get('author') get('username'), avatar postobject get('author') get('avatar') ? postobject get('author') get('avatar') url() null }, likes postobject get('likes') || 0, createdat postobject get('createdat'), image postobject get('image') ? postobject get('image') url() null }; // convert comments to plain objects const fetchedcomments = commentresults map(comment => ({ id comment id, content comment get('content'), author { id comment get('author') id, username comment get('author') get('username'), avatar comment get('author') get('avatar') ? comment get('author') get('avatar') url() null }, createdat comment get('createdat') })); setpost(fetchedpost); setcomments(fetchedcomments); } catch (error) { console error('error fetching post details ', error); toaster create({ title 'error loading post', description error message, type 'error', }); navigate('/feed'); } finally { setisloading(false); } }; if (id) { fetchpostdetails(); } }, \[id, currentuser, navigate]); // function to add a comment const handleaddcomment = async () => { if (!newcomment trim()) return; setiscommenting(true); try { // get the post object const postquery = new parse query('post'); const postobject = await postquery get(id); // create a new comment object const comment = parse object extend('comment'); const comment = new comment(); // set comment data comment set('content', newcomment); comment set('author', currentuser); comment set('post', postobject); // save the comment await comment save(); // add the new comment to the state const newcommentobj = { id comment id, content comment get('content'), author { id currentuser id, username currentuser get('username'), avatar currentuser get('avatar') ? currentuser get('avatar') url() null }, createdat comment get('createdat') }; setcomments(\[ comments, newcommentobj]); setnewcomment(''); toaster create({ title 'comment added', description 'your comment has been posted successfully!', type 'success', }); } catch (error) { console error('error adding comment ', error); toaster create({ title 'error adding comment', description error message, type 'error', }); } finally { setiscommenting(false); } }; // function to like the post const handlelikepost = async () => { try { // get the post const query = new parse query('post'); const postobject = await query get(id); // increment likes postobject increment('likes'); await postobject save(); // update the post in the state setpost({ post, likes post likes + 1 }); } catch (error) { console error('error liking post ', error); toaster create({ title 'error', description 'could not like the post please try again ', type 'error', }); } }; // format date const formatdate = (date) => { return new date(date) tolocalestring(); }; if (isloading) { return ( \<center h="100vh"> \<spinner size="xl" /> \</center> ); } if (!post) { return ( \<center h="100vh"> \<vstack> \<text>post not found\</text> \<button as={routerlink} to="/feed" colorscheme="blue"> back to feed \</button> \</vstack> \</center> ); } return ( \<box maxw="800px" mx="auto" p={4}> \<button as={routerlink} to="/feed" mb={4} variant="ghost"> ← back to feed \</button> {/ post /} \<box border="1px solid" bordercolor="gray 600" p={6} borderradius="md" mb={6}> \<hstack mb={2}> \<avatar src={post author avatar} name={post author username} size="sm" /> \<vstack align="start" spacing={0}> \<text fontweight="bold">{post author username}\</text> \<text fontsize="sm" color="gray 400">{formatdate(post createdat)}\</text> \</vstack> \</hstack> \<text fontsize="lg" my={post image ? 2 4}>{post content}\</text> {/ display post image if available /} {post image && ( \<box my={4}> \<image src={post image} alt="post image" borderradius="md" maxh="500px" w="auto" /> \</box> )} \<hstack spacing={4}> \<button variant="ghost" onclick={handlelikepost} \> ❤️ {post likes} \</button> \<button variant="ghost"> 🔄 share \</button> \</hstack> \</box> {/ comments section /} \<box> \<heading size="md" mb={4}> comments ({comments length}) \</heading> {/ add comment /} \<box mb={6}> \<textarea placeholder="write a comment " value={newcomment} onchange={(e) => setnewcomment(e target value)} mb={2} /> \<button colorscheme="blue" onclick={handleaddcomment} isloading={iscommenting} disabled={!newcomment trim()} \> post comment \</button> \</box> \<hr style={{ marginbottom "1rem" }} /> {/ comments list /} {comments length > 0 ? ( \<vstack align="stretch" spacing={4}> {comments map(comment => ( \<box key={comment id} p={4} bg="gray 700" borderradius="md"> \<hstack mb={2}> \<avatar src={comment author avatar} name={comment author username} size="sm" /> \<text fontweight="bold">{comment author username}\</text> \<text fontsize="sm" color="gray 400">• {formatdate(comment createdat)}\</text> \</hstack> \<text>{comment content}\</text> \</box> ))} \</vstack> ) ( \<text color="gray 400">no comments yet be the first to comment!\</text> )} \</box> \</box> ); } export default postdetailspage; this post detail page provides several important features 1\ post display shows the complete post with author information, content, image, and likes 2\ comment creation allows users to add new comments to the post 3\ comment listing displays all comments in chronological order 4\ like functionality enables users to like the post let's examine the key aspects of how back4app is helping us implement these features \### fetching post details with related objects back4app makes it easy to fetch a post with related information ```javascript // get the post const query = new parse query('post'); query include('author'); const postobject = await query get(id); die include('author') methode ruft automatisch das zugehörige benutzerobjekt zusammen mit dem beitrag ab, wodurch separate abfragen überflüssig werden abfragen von verwandten kommentaren das abfragesystem von back4app ermöglicht es uns, alle kommentare zu einem bestimmten beitrag zu finden // get comments const commentquery = new parse query('comment'); commentquery equalto('post', postobject); commentquery include('author'); commentquery ascending('createdat'); const commentresults = await commentquery find(); die equalto('post', postobject) methode filtert kommentare nur auf die, die mit dem aktuellen beitrag verbunden sind kommentare mit zeigern erstellen einen kommentar mit einer beziehung zu einem beitrag zu erstellen, ist einfach // create a new comment object const comment = parse object extend('comment'); const comment = new comment(); // set comment data comment set('content', newcomment); comment set('author', currentuser); comment set('post', postobject); // save the comment await comment save(); der beitrag feld ist als zeiger auf das beitragsobjekt festgelegt, wodurch die beziehung zwischen dem kommentar und dem beitrag hergestellt wird back4gram projekt finden hier den vollständigen code für ein beispielprojekt für ein soziales netzwerk erstellt mit back4app schritt 5 – implementierung erweiterter like funktionalität mit parse der einfache like zähler, den wir bisher implementiert haben, funktioniert gut für grundlegende funktionalität, hat jedoch einschränkungen benutzer können einen beitrag mehrfach liken ein benutzer kann einen beitrag nicht "entliken" wir können nicht anzeigen, wer einen beitrag geliket hat lass uns ein fortschrittlicheres like system mit einer separaten like klasse erstellen, um individuelle likes zu verfolgen einrichten der like klasse zuerst erstellen sie eine neue klasse in ihrem back4app dashboard klicken sie auf "eine klasse erstellen" im datenbankbrowser geben sie "gefällt mir" als klassennamen ein und wählen sie "benutzerdefiniert" als typ aus fügen sie die folgenden spalten hinzu benutzer (typ zeiger, zielklasse benutzer) beitrag (typ zeiger, zielklasse beitrag) implementierung des like managements in unserem code jetzt aktualisieren wir die funktionalität zum liken von beiträgen in der postdetailspage js datei // add this state at the beginning of the component const \[hasliked, sethasliked] = usestate(false); // add this to the useeffect that fetches post details // check if current user has liked this post const likequery = new parse query('like'); likequery equalto('user', currentuser); likequery equalto('post', postobject); const userlike = await likequery first(); sethasliked(!!userlike); // replace the handlelikepost function with this new version const handlelikepost = async () => { try { // get the post const query = new parse query('post'); const postobject = await query get(id); // check if user has already liked the post const likequery = new parse query('like'); likequery equalto('user', currentuser); likequery equalto('post', postobject); const existinglike = await likequery first(); if (existinglike) { // user already liked the post, so remove the like await existinglike destroy(); // decrement likes count on the post postobject increment('likes', 1); await postobject save(); // update ui sethasliked(false); setpost({ post, likes post likes 1 }); } else { // user hasn't liked the post yet, so add a like const like = parse object extend('like'); const like = new like(); like set('user', currentuser); like set('post', postobject); await like save(); // increment likes count on the post postobject increment('likes'); await postobject save(); // update ui sethasliked(true); setpost({ post, likes post likes + 1 }); } } catch (error) { console error('error toggling like ', error); toaster create({ title 'error', description 'could not process your like please try again ', type 'error', }); } }; aktualisieren sie dann die schaltfläche „gefällt mir“, um den aktuellen status widerzuspiegeln \<button variant="ghost" onclick={handlelikepost} color={hasliked ? "red 500" "inherit"} \> {hasliked ? "❤️" "🤍"} {post likes} \</button> diese implementierung verfolgt individuelle likes mit einer separaten gefällt mir klasse ermöglicht benutzern, likes umzuschalten (liken und nicht liken) zeigt visuell an, ob der aktuelle benutzer den beitrag gemocht hat hält die anzahl der likes für den beitrag für effizientes abfragen aufrecht anzeigen, wer einen beitrag gemocht hat sie können auch eine funktion hinzufügen, um anzuzeigen, wer einen beitrag gemocht hat // add this state const \[likeusers, setlikeusers] = usestate(\[]); const \[showlikes, setshowlikes] = usestate(false); // add this function const fetchlikes = async () => { try { const postquery = new parse query('post'); const postobject = await postquery get(id); const likequery = new parse query('like'); likequery equalto('post', postobject); likequery include('user'); const likes = await likequery find(); const users = likes map(like => { const user = like get('user'); return { id user id, username user get('username'), avatar user get('avatar') ? user get('avatar') url() null }; }); setlikeusers(users); setshowlikes(true); } catch (error) { console error('error fetching likes ', error); } }; fügen sie dann ein ui element hinzu, um die benutzer anzuzeigen, die den beitrag gemocht haben \<text color="blue 400" cursor="pointer" onclick={fetchlikes} fontsize="sm" \> see who liked this post \</text> {showlikes && ( \<box mt={2} p={2} bg="gray 700" borderradius="md"> \<heading size="xs" mb={2}>liked by \</heading> {likeusers length > 0 ? ( \<vstack align="stretch"> {likeusers map(user => ( \<hstack key={user id}> \<avatar size="xs" src={user avatar} name={user username} /> \<text fontsize="sm">{user username}\</text> \</hstack> ))} \</vstack> ) ( \<text fontsize="sm">no likes yet\</text> )} \</box> )} back4gram projekt finden hier den vollständigen code für ein beispielprojekt für ein soziales netzwerk erstellt mit back4app schritt 6 – implementierung von echtzeit updates mit parse live query eine der leistungsstärksten funktionen von back4app ist die möglichkeit, echtzeit updates mit live query zu erhalten dies ermöglicht es ihrer anwendung, sich automatisch zu aktualisieren, wenn neue beiträge oder kommentare erstellt werden, ohne dass eine manuelle aktualisierung erforderlich ist einrichten von live query auf back4app zuerst müssen sie live query für ihre anwendung in back4app aktivieren gehen sie zu ihrem back4app dashboard navigieren sie zu app einstellungen > servereinstellungen scrollen sie nach unten zu "parse live query klassennamen" fügen sie "post" und "kommentar" zur liste der klassen hinzu speichern sie ihre änderungen implementierung von live query für echtzeitbeiträge lass uns unser feedpage js aktualisieren, um live query für echtzeit post updates einzuschließen // add to your import statements import { useeffect, useref } from 'react'; // add to your component const livequerysubscription = useref(null); // add this to your component to set up and clean up the subscription useeffect(() => { const setuplivequery = async () => { if (!currentuser) return; try { console log('setting up live query for posts'); // create a query that will be used for the subscription const query = new parse query('post'); // subscribe to the query livequerysubscription current = await query subscribe(); console log('successfully subscribed to live query'); // handle connection open livequerysubscription current on('open', () => { console log('live query connection opened'); }); // handle new posts livequerysubscription current on('create', (post) => { console log('new post received via live query ', post id); // skip posts from the current user as they're already in the ui if (post get('author') id === currentuser id) { console log('skipping post from current user'); return; } // format the new post const author = post get('author'); const newpost = { id post id, content post get('content'), author { id author id, username author get('username'), avatar author get('avatar') ? author get('avatar') url() null }, image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat') }; // add the new post to the posts state setposts(prevposts => \[newpost, prevposts]); }); // handle post updates livequerysubscription current on('update', (post) => { console log('post updated via live query ', post id); // format the updated post const newlikes = post get('likes') || 0; // update the post in the state setposts(prevposts => prevposts map(p => p id === post id ? { p, likes newlikes } p ) ); }); // handle errors livequerysubscription current on('error', (error) => { console error('live query error ', error); }); } catch (error) { console error('error setting up live query ', error); } }; setuplivequery(); // clean up subscription return () => { if (livequerysubscription current) { console log('unsubscribing from live query'); livequerysubscription current unsubscribe(); } }; }, \[currentuser]); implementierung von live query für echtzeit kommentare ähnlich, lass uns unsere postdetailspage js aktualisieren, um live query für echtzeit kommentare einzuschließen // add to your import statements import { useeffect, useref } from 'react'; // add to your component const livequerysubscription = useref(null); // add this to your component to set up and clean up the subscription useeffect(() => { const setuplivequery = async () => { if (!currentuser || !id) return; try { console log('setting up live query for comments'); // get the post object to use in the query const postquery = new parse query('post'); const postobject = await postquery get(id); // create a query that will be used for the subscription const query = new parse query('comment'); query equalto('post', postobject); // subscribe to the query livequerysubscription current = await query subscribe(); console log('successfully subscribed to live query for comments'); // handle connection open livequerysubscription current on('open', () => { console log('live query connection opened'); }); // handle new comments livequerysubscription current on('create', (comment) => { console log('new comment received via live query ', comment id); // skip comments from the current user as they're already in the ui if (comment get('author') id === currentuser id) { console log('skipping comment from current user'); return; } // format the new comment const author = comment get('author'); const newcomment = { id comment id, content comment get('content'), author { id author id, username author get('username'), avatar author get('avatar') ? author get('avatar') url() null }, createdat comment get('createdat') }; // add the new comment to the comments state setcomments(prevcomments => \[ prevcomments, newcomment]); }); // handle errors livequerysubscription current on('error', (error) => { console error('live query error ', error); }); } catch (error) { console error('error setting up live query for comments ', error); } }; setuplivequery(); // clean up subscription return () => { if (livequerysubscription current) { console log('unsubscribing from live query'); livequerysubscription current unsubscribe(); } }; }, \[currentuser, id]); mit diesen live query implementierungen wird ihr soziales netzwerk jetzt in echtzeit aktualisiert neue beiträge von anderen benutzern erscheinen automatisch im feed die anzahl der likes wird in echtzeit aktualisiert, wenn andere benutzer einen beitrag liken neue kommentare erscheinen automatisch auf der detailseite des beitrags back4gram projekt finden hier den vollständigen code für ein beispielprojekt für ein soziales netzwerk erstellt mit back4app schritt 7 – erweiterte abfragen und leistungsoptimierung wenn ihr soziales netzwerk wächst, wird die leistung zunehmend wichtig lassen sie uns einige fortgeschrittene techniken erkunden, um ihre anwendung mit den leistungsstarken abfragefunktionen von back4app zu optimieren implementierung der paginierung anstatt alle beiträge auf einmal zu laden, implementieren sie die paginierung, um sie in chargen zu laden // add these state variables const \[page, setpage] = usestate(0); const \[hasmoreposts, sethasmoreposts] = usestate(true); const postsperpage = 10; // update your fetchposts function const fetchposts = async (loadmore = false) => { if (!currentuser) return; if (!loadmore) { setisloading(true); } try { const query = new parse query('post'); query include('author'); query descending('createdat'); query limit(postsperpage); query skip(page postsperpage); const results = await query find(); // process and format posts as before const fetchedposts = \[ ]; // update the hasmoreposts state sethasmoreposts(results length === postsperpage); // update the posts state if (loadmore) { setposts(prevposts => \[ prevposts, fetchedposts]); } else { setposts(fetchedposts); } // update the page if this was a "load more" request if (loadmore) { setpage(prevpage => prevpage + 1); } } catch (error) { // handle errors } finally { setisloading(false); } }; // add a load more button at the bottom of your posts list {hasmoreposts && ( \<button onclick={() => fetchposts(true)} variant="outline" isloading={isloading} mt={4} \> load more posts \</button> )} implementierung eines benutzerspezifischen feeds für ein personalisierteres erlebnis möchten sie möglicherweise nur beiträge von benutzern anzeigen, denen der aktuelle benutzer folgt // assuming you have a "follow" class with "follower" and "following" pointers const fetchfollowingposts = async () => { try { // first, find all users that the current user follows const followquery = new parse query('follow'); followquery equalto('follower', currentuser); const followings = await followquery find(); // extract the users being followed const followedusers = followings map(follow => follow\ get('following')); // add the current user to show their posts too followedusers push(currentuser); // create a query for posts from these users const query = new parse query('post'); query containedin('author', followedusers); query include('author'); query descending('createdat'); query limit(20); const results = await query find(); // process results } catch (error) { // handle errors } }; verwendung von cloud funktionen für komplexe operationen für komplexe operationen, die mehrere abfragen erfordern, sollten sie die back4app cloud funktionen in betracht ziehen gehen sie zu ihrem back4app dashboard navigieren sie zu cloud code > cloud funktionen erstellen sie eine neue funktion // example function to get a feed with posts, likes, and comment counts parse cloud define("getfeed", async (request) => { try { const user = request user; const page = request params page || 0; const limit = request params limit || 10; if (!user) { throw new error("user must be logged in"); } // query for posts const query = new parse query("post"); query include("author"); query descending("createdat"); query limit(limit); query skip(page limit); const posts = await query find({ usemasterkey true }); // format posts and get additional info const formattedposts = await promise all(posts map(async (post) => { // check if user has liked this post const likequery = new parse query("like"); likequery equalto("post", post); likequery equalto("user", user); const hasliked = await likequery count({ usemasterkey true }) > 0; // get comment count const commentquery = new parse query("comment"); commentquery equalto("post", post); const commentcount = await commentquery count({ usemasterkey true }); // format post for response return { id post id, content post get("content"), image post get("image") ? post get("image") url() null, author { id post get("author") id, username post get("author") get("username"), avatar post get("author") get("avatar") ? post get("author") get("avatar") url() null }, likes post get("likes") || 0, hasliked hasliked, commentcount commentcount, createdat post get("createdat") }; })); return { posts formattedposts }; } catch (error) { throw new error(`error fetching feed ${error message}`); } }); rufen sie diese cloud funktion von ihrer app aus auf const fetchfeed = async () => { try { setisloading(true); const result = await parse cloud run('getfeed', { page 0, limit 10 }); setposts(result posts); } catch (error) { console error('error fetching feed ', error); toaster create({ title 'error loading feed', description error message, type 'error', }); } finally { setisloading(false); } }; dieser ansatz kann die anzahl der abfragen an die datenbank erheblich reduzieren und die leistung verbessern back4gram projekt finden hier den vollständigen code für ein beispielprojekt für ein soziales netzwerk erstellt mit back4app fazit in diesem tutorial haben sie einen umfassenden sozialen netzwerk feed mit interaktiven funktionen unter verwendung von back4app erstellt lassen sie uns zusammenfassen, was sie erreicht haben datenmodell design ein strukturiertes datenmodell für beiträge, kommentare und likes erstellt feed implementierung eine feed seite erstellt, die beiträge von allen benutzern anzeigt beitragserstellung funktionalität implementiert, damit benutzer beiträge mit text und bildern erstellen können kommentarsystem ein kommentarsystem erstellt, das es benutzern ermöglicht, mit beiträgen zu interagieren gefällt mir funktionalität sowohl einfache als auch erweiterte like systeme implementiert echtzeit updates live query für echtzeit updates von beiträgen und kommentaren hinzugefügt leistungsoptimierung techniken zur verbesserung der leistung untersucht, während ihre app wächst back4app hat bereitgestellt eine sichere und skalierbare datenbank zur speicherung von nutzergenerierten inhalten dateispeicherung für beitragsbilder integrierte benutzerauthentifizierung für sicherheit live abfragen für echtzeit updates cloud funktionen für komplexe backend operationen mit diesen bausteinen hat ihr soziales netzwerk eine solide grundlage, die mit dem wachstum ihrer benutzerbasis skalieren kann nächste schritte um ihr soziales netzwerk weiter zu verbessern, sollten sie diese funktionen in betracht ziehen benachrichtigungen benutzer benachrichtigen, wenn ihre beiträge likes oder kommentare erhalten benutzer following system benutzern erlauben, einander zu folgen und ihre feeds anzupassen hashtags und suche ein hashtag system und erweiterte suchfunktionen implementieren medienreiche beiträge unterstützung für videos, mehrere bilder und andere medientypen analytik engagement und benutzerverhalten verfolgen, um ihre anwendung zu verbessern für den vollständigen code der back4gram sozialnetzwerkanwendung können sie das github repository https //github com/templates back4app/back4gram durch die nutzung der leistungsstarken backend dienste von back4app können sie sich darauf konzentrieren, ein großartiges benutzererlebnis zu schaffen, während die plattform die komplexe infrastruktur für eine soziale netzwerk anwendung verwaltet