Project Templates
Social Network
Flusso e Interazioni per il Tuo Rete Sociale
39 min
introduzione in questo tutorial, imparerai come costruire un feed di social network con funzionalità interattive come post, commenti e mi piace utilizzando back4app come servizio backend queste funzionalità formano il nucleo di qualsiasi applicazione di social networking, consentendo agli utenti di condividere contenuti e interagire con altri utenti back4app è una piattaforma backend as a service (baas) costruita su parse server che fornisce agli sviluppatori un'infrastruttura robusta per creare applicazioni scalabili senza gestire il codice lato server le sue capacità di database in tempo reale e la gestione degli utenti integrata la rendono un'ottima scelta per sviluppare applicazioni di social networking alla fine di questo tutorial, avrai implementato un feed sociale completamente funzionale che consente agli utenti di creare post con testo e immagini visualizzare post di altri utenti in un feed mettere mi piace ai post commentare i post visualizzare i dettagli del post con i commenti imparerai come strutturare in modo efficiente le classi del tuo database, implementare aggiornamenti in tempo reale e creare un'interfaccia utente reattiva che fornisce un'esperienza fluida su diversi dispositivi prerequisiti per completare questo tutorial, avrai bisogno di un account back4app puoi registrarti per un account gratuito su back4app com https //www back4app com un progetto back4app configurato puoi imparare a creare un nuovo progetto seguendo la nostra guida per iniziare con back4app https //www back4app com/docs/get started/welcome node js installato sulla tua macchina locale conoscenze di base di javascript e react js un sistema di autenticazione funzionante se non ne hai ancora impostato uno, segui il nostro sistema di autenticazione per reti sociali tutorial prima familiarità con i concetti moderni di sviluppo web (componenti, gestione dello stato, ecc ) progetto back4gram trova qui il codice completo per un progetto campione di rete sociale costruito con back4app passo 1 – comprendere il modello dati prima di scrivere qualsiasi codice, è importante comprendere il modello di dati per il nostro social network avremo bisogno di diverse classi parse per costruire la funzionalità del nostro feed classe post la post classe memorizzerà tutte le informazioni relative ai post autore un puntatore all'utente che ha creato il post contenuto il contenuto testuale del post immagine un file immagine opzionale mi piace un numero che rappresenta quanti mi piace ha il post commenti un array di puntatori agli oggetti commento (opzionale, può essere interrogato separatamente) creato il gestito automaticamente da back4app aggiornato il gestito automaticamente da back4app classe commento il commento classe memorizzerà i commenti sui post autore un puntatore all'utente che ha scritto il commento post un puntatore al post su cui si sta commentando contenuto il contenuto testuale del commento creatoil gestito automaticamente da back4app aggiornatoil gestito automaticamente da back4app tracciamento dei mi piace abbiamo due opzioni per tracciare i mi piace contatore semplice memorizza un conteggio dei mi piace su ogni post (più semplice ma meno dettagliato) registrazioni dei mi piace crea una separata classe mi piace , che traccia ogni singolo mi piace (più dettagliato) per questo tutorial, implementeremo entrambi gli approcci, iniziando con il contatore più semplice e poi mostrando come implementare registrazioni individuali dei mi piace per funzionalità più avanzate iniziamo impostando queste classi in back4app passo 2 – impostare il modello dati in back4app in questo passo, creerai le classi necessarie nel tuo database back4app creazione della classe post accedi al tuo dashboard di back4app e naviga verso il tuo progetto nella barra laterale sinistra, clicca su "database" per aprire il browser del database clicca sul pulsante "crea una classe" in cima alla pagina nella finestra modale che appare, inserisci "post" come nome della classe e seleziona "personalizzato" come tipo poi clicca su "crea classe" \[immagine modale crea classe back4app con "post" inserito come nome della classe] ora, aggiungi le seguenti colonne alla tua classe post clicca su "aggiungi colonna" crea le seguenti colonne autore (tipo puntatore, classe target user) contenuto (tipo stringa) immagine (tipo file) mi piace (tipo numero, valore predefinito 0) creazione della classe commento clicca di nuovo su "crea una classe" inserisci "commento" come nome della classe e seleziona "personalizzato" come tipo aggiungi le seguenti colonne autore (tipo puntatore, classe target user) post (tipo puntatore, classe target post) contenuto (tipo stringa) impostazione delle autorizzazioni a livello di classe per motivi di sicurezza, impostiamo le autorizzazioni appropriate per le nostre classi nel database browser, seleziona la classe post clicca sul pulsante "sicurezza" configura le autorizzazioni lettura pubblica sì (chiunque può visualizzare i post) scrittura pubblica no (solo gli utenti autenticati possono creare post) aggiungi campo pubblico no trova, ottieni, crea, aggiorna, elimina richiedi autenticazione dell'utente ripeti impostazioni simili per la classe comment ora che abbiamo impostato il nostro modello di dati, implementiamo i componenti front end per il nostro feed di social network la pagina del feed è il cuore di qualsiasi rete sociale mostra i post degli utenti e consente l'interazione creiamo un componente feed che recupera e visualizza i post crea un file chiamato 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; questo componente feed include diverse funzionalità chiave controllo di autenticazione garantisce che solo gli utenti con accesso possano visualizzare il feed creazione post consente agli utenti di creare nuovi post con testo e immagini opzionali caricamento immagini gestisce la selezione, l'anteprima e il caricamento delle immagini utilizzando parse file visualizzazione feed mostra i post di tutti gli utenti in ordine cronologico inverso funzionalità di mi piace consente agli utenti di mettere mi piace ai post utilizzando un semplice approccio contatore navigazione fornisce collegamenti ad altre parti dell'applicazione esaminiamo come back4app ci sta aiutando a implementare queste funzionalità creazione post con parse back4app rende semplice la creazione di post con immagini // 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 gestisce automaticamente i caricamenti di file, lo stoccaggio e la generazione di url, rendendo facile aggiungere immagini ai post recupero post con parse query il sistema di query di back4app rende facile recuperare e visualizzare i post // 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(); il include('author') metodo è particolarmente potente, poiché include automaticamente l'oggetto user referenziato con ogni post, riducendo la necessità di più query progetto back4gram trova qui il codice completo per un progetto campione di social network realizzato con back4app passo 4 – implementazione della vista dettagli post con commenti ora, creiamo una pagina di dettagli post che visualizza un singolo post con i suoi commenti e consente agli utenti di aggiungere nuovi commenti crea un file chiamato 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); il include('author') metodo recupera automaticamente l'oggetto user correlato insieme al post, eliminando la necessità di query separate interrogare commenti correlati il sistema di query di back4app ci consente di trovare tutti i commenti correlati a un post specifico // get comments const commentquery = new parse query('comment'); commentquery equalto('post', postobject); commentquery include('author'); commentquery ascending('createdat'); const commentresults = await commentquery find(); il equalto('post', postobject) metodo filtra i commenti per includere solo quelli correlati al post attuale creare commenti con punteri creare un commento con una relazione a un post è semplice // 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(); fine del documento il post campo è impostato come un puntatore all'oggetto post, stabilendo la relazione tra il commento e il post progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 5 – implementazione della funzionalità avanzata di mi piace con parse il semplice contatore di mi piace che abbiamo implementato finora funziona bene per la funzionalità di base, ma ha delle limitazioni gli utenti possono mettere mi piace a un post più volte un utente non può "togliere mi piace" a un post non possiamo mostrare chi ha messo mi piace a un post creiamo un sistema di mi piace più avanzato utilizzando una classe like per tracciare i mi piace individuali impostazione della classe like per prima cosa, crea una nuova classe nel tuo dashboard di back4app clicca su "crea una classe" nel database browser inserisci "like" come nome della classe e seleziona "personalizzato" come tipo aggiungi le seguenti colonne utente (tipo puntatore, classe di destinazione user) post (tipo puntatore, classe di destinazione post) implementazione della gestione dei like nel nostro codice ora, aggiorniamo la nostra funzionalità di like ai post nel postdetailspage js file // 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', }); } }; quindi, aggiorna il pulsante di mi piace per riflettere lo stato attuale \<button variant="ghost" onclick={handlelikepost} color={hasliked ? "red 500" "inherit"} \> {hasliked ? "❤️" "🤍"} {post likes} \</button> questa implementazione traccia i mi piace individuali utilizzando un mi piace classe consente agli utenti di attivare e disattivare i mi piace (mi piace e non mi piace) indica visivamente se l'utente attuale ha messo mi piace al post mantiene il conteggio dei mi piace sul post per una query efficiente mostrando chi ha messo mi piace a un post puoi anche aggiungere funzionalità per mostrare chi ha messo mi piace a un post // 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); } }; poi aggiungi un elemento ui per visualizzare gli utenti che hanno messo mi piace al post \<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> )} progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 6 – implementazione degli aggiornamenti in tempo reale con parse live query una delle funzionalità più potenti di back4app è la possibilità di ricevere aggiornamenti in tempo reale con live query questo consente alla tua applicazione di aggiornarsi automaticamente quando vengono creati nuovi post o commenti senza la necessità di un aggiornamento manuale impostare live query su back4app per prima cosa, devi abilitare live query per la tua applicazione in back4app vai al tuo dashboard di back4app naviga su impostazioni app > impostazioni server scorri verso il basso fino a "parse live query class names" aggiungi "post" e "comment" alla lista delle classi salva le tue modifiche implementazione di live query per post in tempo reale aggiorniamo il nostro feedpage js per includere live query per aggiornamenti post in tempo reale // 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]); implementazione di live query per commenti in tempo reale allo stesso modo, aggiorniamo il nostro postdetailspage js per includere live query per commenti in tempo reale // 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]); con queste implementazioni della live query, la tua rete sociale si aggiornerà ora in tempo reale i nuovi post di altri utenti appariranno automaticamente nel feed il conteggio dei 'mi piace' si aggiornerà in tempo reale quando altri utenti metteranno 'mi piace' a un post i nuovi commenti appariranno automaticamente nella pagina dei dettagli del post progetto back4gram trova qui il codice completo per un progetto campione di social network realizzato con back4app passo 7 – query avanzate e ottimizzazione delle prestazioni man mano che la tua rete sociale cresce, le prestazioni diventano sempre più importanti esploriamo alcune tecniche avanzate per ottimizzare la tua applicazione utilizzando le potenti capacità di query di back4app implementazione della pagina invece di caricare tutti i post in una volta, implementa la paginazione per caricarli a gruppi // 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> )} implementazione di un feed specifico per l'utente per un'esperienza più personalizzata, potresti voler mostrare post solo dagli utenti che l'utente attuale segue // 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 } }; utilizzare le funzioni cloud per operazioni complesse per operazioni complesse che richiederebbero più query, considera di utilizzare le funzioni cloud di back4app vai al tuo dashboard di back4app naviga su cloud code > funzioni cloud crea una nuova funzione // 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}`); } }); quindi chiama questa cloud function dalla tua app 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); } }; questo approccio può ridurre significativamente il numero di query al database e migliorare le prestazioni progetto back4gram trova qui il codice completo per un progetto campione di social network realizzato con back4app conclusione in questo tutorial, hai costruito un feed di social network completo con funzionalità interattive utilizzando back4app rivediamo cosa hai realizzato progettazione del modello dati creato un modello di dati strutturato per post, commenti e like implementazione del feed costruita una pagina feed che visualizza i post di tutti gli utenti creazione di post implementata la funzionalità per gli utenti di creare post con testo e immagini sistema di commenti creato un sistema di commenti che consente agli utenti di interagire con i post funzionalità di like implementati sia sistemi di like semplici che avanzati aggiornamenti in tempo reale aggiunta live query per aggiornamenti in tempo reale di post e commenti ottimizzazione delle prestazioni esplorate tecniche per migliorare le prestazioni man mano che la tua app cresce back4app ha fornito un database sicuro e scalabile per memorizzare contenuti generati dagli utenti memorizzazione di file per le immagini dei post autenticazione utente integrata per la sicurezza query in tempo reale per aggiornamenti in tempo reale funzioni cloud per operazioni backend complesse con questi elementi fondamentali in atto, la tua rete sociale ha una solida base che può scalare man mano che cresce il tuo numero di utenti prossimi passi per migliorare ulteriormente la tua rete sociale, considera di implementare queste funzionalità notifiche avvisa gli utenti quando i loro post ricevono mi piace o commenti sistema di segui utenti consenti agli utenti di seguire l'un l'altro e personalizzare i loro feed hashtag e ricerca implementa un sistema di hashtag e funzionalità di ricerca avanzate post ricchi di media supporta video, più immagini e altri tipi di media analisi monitora l'engagement e il comportamento degli utenti per migliorare la tua applicazione per il codice completo dell'applicazione di rete sociale back4gram, puoi controllare il repository github https //github com/templates back4app/back4gram sfruttando i potenti servizi backend di back4app, puoi concentrarti sulla creazione di una grande esperienza utente mentre la piattaforma gestisce l'infrastruttura complessa necessaria per un'applicazione di social networking